├── .gitignore ├── Makefile ├── Package.swift ├── ProtectedBrowser.plist ├── README.md ├── Sources ├── ProtectedBrowser │ └── Tweak.x.swift └── ProtectedBrowserC │ ├── Tweak.m │ └── include │ ├── Tweak.h │ └── module.modulemap ├── control └── protectedbrowser ├── Makefile ├── Package.swift ├── Resources ├── Behaviour.plist ├── Info.plist ├── PrefIcon.png ├── PrefIcon@2x.png ├── PrefIcon@3x.png ├── Root.plist └── defaults.plist ├── Sources ├── protectedbrowser │ ├── PBBehaviourController.swift │ ├── PBPickerController.swift │ └── RootListController.swift └── protectedbrowserC │ ├── include │ ├── module.modulemap │ └── protectedbrowser.h │ └── protectedbrowser.m └── layout └── Library └── PreferenceLoader └── Preferences └── protectedbrowser.plist /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | 7 | .theos 8 | .swiftpm 9 | protectedbrowser/.theos 10 | protectedbrowser/.swiftpm 11 | /packages 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | THEOS_DEVICE_IP = localhost -p 2222 3 | INSTALL_TARGET_PROCESSES = SpringBoard 4 | TARGET = iphone:clang:15.5:14.4 5 | PACKAGE_VERSION = 1.2.4 6 | 7 | include $(THEOS)/makefiles/common.mk 8 | 9 | TWEAK_NAME = ProtectedBrowser 10 | 11 | ProtectedBrowser_EXTRA_FRAMEWORKS = AltList 12 | ProtectedBrowser_FRAMEWORKS = MobileCoreServices 13 | ProtectedBrowser_FILES = $(shell find Sources/ProtectedBrowser -name '*.swift') $(shell find Sources/ProtectedBrowserC -name '*.m' -o -name '*.c' -o -name '*.mm' -o -name '*.cpp') 14 | ProtectedBrowser_SWIFTFLAGS = -ISources/ProtectedBrowserC/include 15 | ProtectedBrowser_CFLAGS = -fobjc-arc -ISources/ProtectedBrowserC/include 16 | 17 | include $(THEOS_MAKE_PATH)/tweak.mk 18 | SUBPROJECTS += protectedbrowser 19 | include $(THEOS_MAKE_PATH)/aggregate.mk 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | import Foundation 5 | 6 | let projectDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() 7 | 8 | @dynamicMemberLookup struct TheosConfiguration { 9 | private let dict: [String: String] 10 | init(at path: String) { 11 | let configURL = URL(fileURLWithPath: path, relativeTo: projectDir) 12 | guard let infoString = try? String(contentsOf: configURL) else { 13 | fatalError(""" 14 | Could not find Theos SPM config. Have you run `make spm` yet? 15 | """) 16 | } 17 | let pairs = infoString.split(separator: "\n").map { 18 | $0.split( 19 | separator: "=", maxSplits: 1, 20 | omittingEmptySubsequences: false 21 | ).map(String.init) 22 | }.map { ($0[0], $0[1]) } 23 | dict = Dictionary(uniqueKeysWithValues: pairs) 24 | } 25 | subscript( 26 | key: String, 27 | or defaultValue: @autoclosure () -> String? = nil 28 | ) -> String { 29 | if let value = dict[key] { 30 | return value 31 | } else if let def = defaultValue() { 32 | return def 33 | } else { 34 | fatalError(""" 35 | Could not get value of key '\(key)' from Theos SPM config. \ 36 | Try running `make spm` again. 37 | """) 38 | } 39 | } 40 | subscript(dynamicMember key: String) -> String { self[key] } 41 | } 42 | let conf = TheosConfiguration(at: ".theos/spm_config") 43 | 44 | let theosPath = conf.theos 45 | let sdk = conf.sdk 46 | let resourceDir = conf.swiftResourceDir 47 | let deploymentTarget = conf.deploymentTarget 48 | let triple = "arm64-apple-ios\(deploymentTarget)" 49 | 50 | let libFlags: [String] = [ 51 | "-F\(theosPath)/vendor/lib", "-F\(theosPath)/lib", 52 | "-I\(theosPath)/vendor/include", "-I\(theosPath)/include" 53 | ] 54 | 55 | let cFlags: [String] = libFlags + [ 56 | "-target", triple, "-isysroot", sdk, 57 | "-Wno-unused-command-line-argument", "-Qunused-arguments", 58 | ] 59 | 60 | let cxxFlags: [String] = [ 61 | ] 62 | 63 | let swiftFlags: [String] = libFlags + [ 64 | "-target", triple, "-sdk", sdk, "-resource-dir", resourceDir, 65 | ] 66 | 67 | let package = Package( 68 | name: "ProtectedBrowser", 69 | platforms: [.iOS(deploymentTarget)], 70 | products: [ 71 | .library( 72 | name: "ProtectedBrowser", 73 | targets: ["ProtectedBrowser"] 74 | ), 75 | ], 76 | targets: [ 77 | .target( 78 | name: "ProtectedBrowserC", 79 | cSettings: [.unsafeFlags(cFlags)], 80 | cxxSettings: [.unsafeFlags(cxxFlags)] 81 | ), 82 | .target( 83 | name: "ProtectedBrowser", 84 | dependencies: ["ProtectedBrowserC"], 85 | swiftSettings: [.unsafeFlags(swiftFlags)] 86 | ), 87 | ] 88 | ) 89 | -------------------------------------------------------------------------------- /ProtectedBrowser.plist: -------------------------------------------------------------------------------- 1 | { 2 | Filter = { 3 | Bundles = ( 4 | "com.apple.WebKit", 5 | ); 6 | }; 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProtectedBrowser 2 | Prevent JS injection on third party in-app browsers (instagram, TikTok, etc). 3 | -------------------------------------------------------------------------------- /Sources/ProtectedBrowser/Tweak.x.swift: -------------------------------------------------------------------------------- 1 | import Orion 2 | import WebKit 3 | import ProtectedBrowserC 4 | 5 | enum ProtectionMode: Int { 6 | case onlyHarmful = 0, all = 1, forceSafari = 2 7 | } 8 | 9 | struct Settings { 10 | static var isEnabled: Bool! 11 | static var protectionMode: ProtectionMode! 12 | static var monitor: Bool! 13 | static var enabledApps: [String]! 14 | static var allApps: Bool! 15 | } 16 | 17 | struct ExternalScripts: HookGroup {} 18 | struct AllScripts: HookGroup {} 19 | struct ForceSafari: HookGroup {} 20 | struct Monitor: HookGroup {} 21 | 22 | //MARK: - Force open Safari. 23 | class ForceSafari_Hook: ClassHook { 24 | typealias Group = ForceSafari 25 | 26 | func _didStartProvisionalLoadForMainFrame() { 27 | orig._didStartProvisionalLoadForMainFrame() 28 | 29 | guard let url = target.url else { 30 | return 31 | } 32 | 33 | UIApplication.shared.open(url) 34 | } 35 | } 36 | 37 | //MARK: - Monitoring for JS injection. 38 | class Monitor_Hook: ClassHook { 39 | typealias Group = Monitor 40 | 41 | func _didCommitLoadForMainFrame() { 42 | orig._didCommitLoadForMainFrame() 43 | 44 | let userContent = target.configuration.userContentController 45 | 46 | guard !userContent.userScripts.isEmpty else { 47 | return 48 | } 49 | 50 | guard !UserDefaults.standard.bool(forKey: "ProtectedBrowser_\(Bundle.main.bundleIdentifier!).alreadyAlerted") else { 51 | return 52 | } 53 | 54 | presentDetectedExternalJSAlert() 55 | } 56 | 57 | //orion: new 58 | func presentDetectedExternalJSAlert() { 59 | let alert = UIAlertController(title: "ProtectedBrowser", message: "ProtectedBrowser has detected that this app injects external JavaScript code that may be invasive/malicious.", preferredStyle: .alert) 60 | 61 | alert.addAction(UIAlertAction(title: "Disable JS in this app", style: .destructive, handler: { action in 62 | guard let url = URL(string: UIApplication.openSettingsURLString) else { 63 | return 64 | } 65 | 66 | if UIApplication.shared.canOpenURL(url) { 67 | UIApplication.shared.open(url) 68 | } 69 | })) 70 | 71 | alert.addAction(UIAlertAction(title: "Don't alert me again", style: .default, handler: { action in 72 | UserDefaults.standard.set(true, forKey: "ProtectedBrowser_\(Bundle.main.bundleIdentifier!).alreadyAlerted") 73 | })) 74 | 75 | alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel)) 76 | 77 | guard let ancestor = target._viewControllerForAncestor() else { 78 | return 79 | } 80 | 81 | ancestor.present(alert, animated: true) 82 | } 83 | } 84 | 85 | //MARK: - Removing external JS 86 | class ExternalJS_Hook: ClassHook { 87 | typealias Group = ExternalScripts 88 | 89 | func addUserScript(_ script: WKUserScript) { 90 | target.removeAllUserScripts() 91 | return 92 | } 93 | } 94 | 95 | //MARK: - Overkill 96 | class AllJS_Hook: ClassHook { 97 | typealias Group = AllScripts 98 | 99 | func _allowsJavaScriptMarkup() -> Bool { 100 | return false 101 | } 102 | } 103 | 104 | //MARK: - Preferences 105 | fileprivate func prefsDict() -> [String : AnyObject]? { 106 | var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml 107 | 108 | let path = "/var/mobile/Library/Preferences/com.ginsu.protectedbrowser-new.plist" 109 | 110 | if !FileManager().fileExists(atPath: path) { 111 | try? FileManager().copyItem(atPath: "Library/PreferenceBundles/protectedbrowser.bundle/defaults.plist", 112 | toPath: path) 113 | } 114 | 115 | let plistURL = URL(fileURLWithPath: path) 116 | 117 | guard let plistXML = try? Data(contentsOf: plistURL) else { 118 | return nil 119 | } 120 | 121 | guard let plistDict = try! PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String : AnyObject] else { 122 | return nil 123 | } 124 | 125 | return plistDict 126 | } 127 | 128 | fileprivate func readPrefs() { 129 | 130 | let dict = prefsDict() ?? [String : AnyObject]() 131 | 132 | //Reading values 133 | Settings.isEnabled = dict["isEnabled"] as? Bool ?? true 134 | Settings.protectionMode = ProtectionMode(rawValue: dict["protectionMode"] as? Int ?? 0) 135 | Settings.monitor = dict["monitor"] as? Bool ?? true 136 | Settings.enabledApps = dict["enabledApps"] as? [String] ?? [""] 137 | Settings.allApps = dict["allApps"] as? Bool ?? false 138 | } 139 | 140 | struct ProtectedBrowser: Tweak { 141 | init() { 142 | readPrefs() 143 | 144 | if Settings.isEnabled { 145 | guard let identifier = Bundle.main.bundleIdentifier else { 146 | return 147 | } 148 | 149 | guard !identifier.hasPrefix("com.apple.") else { 150 | return 151 | } 152 | 153 | var canProtect: Bool { 154 | if Settings.allApps { 155 | guard let proxy = LSApplicationProxy.applicationProxy(forIdentifier: identifier) as? LSApplicationProxy else { 156 | return false 157 | } 158 | 159 | if proxy.atl_isUserApplication() { 160 | return true 161 | } 162 | } else { 163 | if Settings.enabledApps.contains(identifier) { 164 | return true 165 | } 166 | } 167 | 168 | return false 169 | } 170 | 171 | guard canProtect else { 172 | if Settings.monitor { 173 | Monitor().activate() 174 | } 175 | return 176 | } 177 | 178 | switch Settings.protectionMode { 179 | case .all: 180 | ExternalScripts().activate() 181 | AllScripts().activate() 182 | break 183 | case .onlyHarmful: 184 | ExternalScripts().activate() 185 | break 186 | case .forceSafari: 187 | ForceSafari().activate() 188 | break 189 | default: 190 | ExternalScripts().activate() 191 | break 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Sources/ProtectedBrowserC/Tweak.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | __attribute__((constructor)) static void init() { 4 | // Initialize Orion - do not remove this line. 5 | orion_init(); 6 | // Custom initialization code goes here. 7 | } 8 | -------------------------------------------------------------------------------- /Sources/ProtectedBrowserC/include/Tweak.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface UIView (Private) 5 | - (UIViewController *)_viewControllerForAncestor; 6 | @end 7 | 8 | @interface LSApplicationProxy (AltList) 9 | + (id)applicationProxyForIdentifier:(NSString *)arg1 ; 10 | - (BOOL)atl_isUserApplication; 11 | @end 12 | -------------------------------------------------------------------------------- /Sources/ProtectedBrowserC/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module ProtectedBrowserC { 2 | umbrella "." 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.ginsu.protectedbrowser 2 | Name: ProtectedBrowser 3 | Version: 0.0.1 4 | Architecture: iphoneos-arm 5 | Description: Disable dangerous JS injection in 3rd party in-app browsers 6 | Maintainer: Ginsu 7 | Author: Ginsu 8 | Section: Tweaks 9 | Depends: ${ORION}, firmware (>= 13.0), preferenceloader, com.ginsu.libgscommon, com.opa334.altlist 10 | -------------------------------------------------------------------------------- /protectedbrowser/Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | TARGET = iphone:clang:15.5:14.4 3 | 4 | include $(THEOS)/makefiles/common.mk 5 | 6 | BUNDLE_NAME = protectedbrowser 7 | 8 | protectedbrowser_LIBRARIES = gscommon 9 | protectedbrowser_EXTRA_FRAMEWORKS = AltList 10 | protectedbrowser_FILES = $(shell find Sources/protectedbrowser -name '*.swift') $(shell find Sources/protectedbrowserC -name '*.m' -o -name '*.c' -o -name '*.mm' -o -name '*.cpp') 11 | protectedbrowser_SWIFTFLAGS = -ISources/protectedbrowserC/include 12 | protectedbrowser_INSTALL_PATH = /Library/PreferenceBundles 13 | protectedbrowser_CFLAGS = -fobjc-arc 14 | 15 | include $(THEOS_MAKE_PATH)/bundle.mk 16 | -------------------------------------------------------------------------------- /protectedbrowser/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | 3 | import PackageDescription 4 | import Foundation 5 | 6 | let projectDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() 7 | 8 | @dynamicMemberLookup struct TheosConfiguration { 9 | private let dict: [String: String] 10 | init(at path: String) { 11 | let configURL = URL(fileURLWithPath: path, relativeTo: projectDir) 12 | guard let infoString = try? String(contentsOf: configURL) else { 13 | fatalError(""" 14 | Could not find Theos SPM config. Have you run `make spm` yet? 15 | """) 16 | } 17 | let pairs = infoString.split(separator: "\n").map { 18 | $0.split( 19 | separator: "=", maxSplits: 1, 20 | omittingEmptySubsequences: false 21 | ).map(String.init) 22 | }.map { ($0[0], $0[1]) } 23 | dict = Dictionary(uniqueKeysWithValues: pairs) 24 | } 25 | subscript( 26 | key: String, 27 | or defaultValue: @autoclosure () -> String? = nil 28 | ) -> String { 29 | if let value = dict[key] { 30 | return value 31 | } else if let def = defaultValue() { 32 | return def 33 | } else { 34 | fatalError(""" 35 | Could not get value of key '\(key)' from Theos SPM config. \ 36 | Try running `make spm` again. 37 | """) 38 | } 39 | } 40 | subscript(dynamicMember key: String) -> String { self[key] } 41 | } 42 | let conf = TheosConfiguration(at: ".theos/spm_config") 43 | 44 | let theosPath = conf.theos 45 | let sdk = conf.sdk 46 | let resourceDir = conf.swiftResourceDir 47 | let deploymentTarget = conf.deploymentTarget 48 | let triple = "arm64-apple-ios\(deploymentTarget)" 49 | 50 | let libFlags: [String] = [ 51 | "-F\(theosPath)/vendor/lib", "-F\(theosPath)/lib", 52 | "-I\(theosPath)/vendor/include", "-I\(theosPath)/include" 53 | ] 54 | 55 | let cFlags: [String] = libFlags + [ 56 | "-target", triple, "-isysroot", sdk, 57 | "-Wno-unused-command-line-argument", "-Qunused-arguments", 58 | ] 59 | 60 | let cxxFlags: [String] = [ 61 | ] 62 | 63 | let swiftFlags: [String] = libFlags + [ 64 | "-target", triple, "-sdk", sdk, "-resource-dir", resourceDir, 65 | ] 66 | 67 | let package = Package( 68 | name: "protectedbrowser", 69 | platforms: [.iOS("12.2")], 70 | products: [ 71 | .library( 72 | name: "protectedbrowser", 73 | targets: ["protectedbrowser"] 74 | ), 75 | ], 76 | targets: [ 77 | .target( 78 | name: "protectedbrowserC", 79 | cSettings: [.unsafeFlags(cFlags)], 80 | cxxSettings: [.unsafeFlags(cxxFlags)] 81 | ), 82 | .target( 83 | name: "protectedbrowser", 84 | dependencies: ["protectedbrowserC"], 85 | swiftSettings: [.unsafeFlags(swiftFlags)] 86 | ), 87 | ] 88 | ) 89 | -------------------------------------------------------------------------------- /protectedbrowser/Resources/Behaviour.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSGroupCell 10 | footerText 11 | Choose which level of protection you'd like to have. Disabling all scripts might cause unexpected behaviour in a lot of apps, so use that with caution. 12 | 13 | 14 | cell 15 | PSLinkListCell 16 | detail 17 | protectedbrowser.PBPickerController 18 | default 19 | 0 20 | defaults 21 | com.ginsu.protectedbrowser-new 22 | label 23 | Protection mode 24 | key 25 | protectionMode 26 | validValues 27 | 28 | 0 29 | 1 30 | 2 31 | 32 | validTitles 33 | 34 | Disable harmful scripts (Safe) 35 | Disable all scripts (Safe, may cause issues) 36 | Force open in Safari (Safest) 37 | 38 | 39 | 40 | cell 41 | PSGroupCell 42 | footerText 43 | This option will alert you if external JavaScript was injected into a webpage while the app was unprotected. 44 | 45 | 46 | cell 47 | PSSwitchCell 48 | cellClass 49 | PSSubtitleSwitchTableCell 50 | cellSubtitleText 51 | Alerts you if an app injects JS 52 | default 53 | 54 | defaults 55 | com.ginsu.protectedbrowser-new 56 | key 57 | monitor 58 | label 59 | Alert on JS injection 60 | 61 | 62 | cell 63 | PSGroupCell 64 | label 65 | Apps 66 | footerText 67 | Choose which apps the above settings will be applied to. 68 | 69 | 70 | cell 71 | PSSwitchCell 72 | cellClass 73 | PSSubtitleSwitchTableCell 74 | cellSubtitleText 75 | Protects all 3rd party apps 76 | default 77 | 78 | defaults 79 | com.ginsu.protectedbrowser-new 80 | key 81 | allApps 82 | label 83 | Protect all apps 84 | id 85 | allAppsStatus 86 | 87 | 88 | bundle 89 | AltList 90 | cell 91 | PSLinkListCell 92 | detail 93 | ATLApplicationListMultiSelectionController 94 | key 95 | enabledApps 96 | defaults 97 | com.ginsu.protectedbrowser-new 98 | sections 99 | 100 | 101 | sectionType 102 | User 103 | 104 | 105 | isController 106 | 107 | overridePrincipalClass 108 | 109 | label 110 | Protected apps 111 | useSearchBar 112 | 113 | alphabeticIndexingEnabled 114 | 115 | dynamicRule 116 | allAppsStatus,s,0 117 | 118 | 119 | title 120 | Behaviour 121 | 122 | 123 | -------------------------------------------------------------------------------- /protectedbrowser/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | protectedbrowser 9 | CFBundleIdentifier 10 | com.ginsu.protectedbrowser-new 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1.0 21 | NSPrincipalClass 22 | protectedbrowser.RootListController 23 | 24 | 25 | -------------------------------------------------------------------------------- /protectedbrowser/Resources/PrefIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/ProtectedBrowser/7926b2b2c7f3ddc312abae7b8b5465436aead45d/protectedbrowser/Resources/PrefIcon.png -------------------------------------------------------------------------------- /protectedbrowser/Resources/PrefIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/ProtectedBrowser/7926b2b2c7f3ddc312abae7b8b5465436aead45d/protectedbrowser/Resources/PrefIcon@2x.png -------------------------------------------------------------------------------- /protectedbrowser/Resources/PrefIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/ProtectedBrowser/7926b2b2c7f3ddc312abae7b8b5465436aead45d/protectedbrowser/Resources/PrefIcon@3x.png -------------------------------------------------------------------------------- /protectedbrowser/Resources/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | cell 9 | PSSwitchCell 10 | cellClass 11 | PSSubtitleSwitchTableCell 12 | cellSubtitleText 13 | Enable/Disable the tweak 14 | default 15 | 16 | defaults 17 | com.ginsu.protectedbrowser-new 18 | key 19 | isEnabled 20 | label 21 | Enabled 22 | 23 | 24 | cell 25 | PSGroupCell 26 | label 27 | Configuration 28 | 29 | 30 | cell 31 | PSLinkCell 32 | detail 33 | protectedbrowser.PBBehaviourController 34 | isController 35 | 36 | label 37 | Behaviour 38 | 39 | 40 | cell 41 | PSGroupCell 42 | label 43 | Repo 44 | 45 | 46 | cell 47 | PSButtonCell 48 | cellClass 49 | GSRepoCell 50 | link 51 | https://repo.ginsu.dev 52 | title 53 | Add Ginsu's repo 54 | 55 | 56 | cell 57 | PSGroupCell 58 | footerText 59 | © 2022 Ginsu (@ginsudev) 60 | Icon by @bank5ia 61 | footerAlignment 62 | 1 63 | 64 | 65 | title 66 | ProtectedBrowser 67 | 68 | 69 | -------------------------------------------------------------------------------- /protectedbrowser/Resources/defaults.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isEnabled 6 | 7 | protectionMode 8 | 0 9 | monitor 10 | 11 | allApps 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowser/PBBehaviourController.swift: -------------------------------------------------------------------------------- 1 | import Preferences 2 | import Foundation 3 | import protectedbrowserC 4 | 5 | class PBBehaviourController: PSListController { 6 | private var name = "protectedbrowser" 7 | 8 | override var specifiers: NSMutableArray? { 9 | get { 10 | if let specifiers = value(forKey: "_specifiers") as? NSMutableArray { 11 | self.collectDynamicSpecifiersFromArray(specifiers) 12 | return specifiers 13 | } else { 14 | let specifiers = loadSpecifiers(fromPlistName: "Behaviour", target: self) 15 | setValue(specifiers, forKey: "_specifiers") 16 | self.collectDynamicSpecifiersFromArray(specifiers!) 17 | return specifiers 18 | } 19 | } 20 | set { 21 | super.specifiers = newValue 22 | } 23 | } 24 | 25 | var hasDynamicSpecifiers: Bool! 26 | var dynamicSpecifiers = [String : [PSSpecifier]]() 27 | var hiddenSpecifiers = [PSSpecifier]() 28 | 29 | override func reloadSpecifiers() { 30 | super.reloadSpecifiers() 31 | self.collectDynamicSpecifiersFromArray(self.specifiers!) 32 | } 33 | 34 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 35 | guard hasDynamicSpecifiers else { 36 | return UITableView.automaticDimension 37 | } 38 | 39 | if let dynamicSpecifier = specifier(at: indexPath) { 40 | for array in dynamicSpecifiers.values { 41 | if array.contains(dynamicSpecifier) { 42 | let shouldHide = shouldHideSpecifier(dynamicSpecifier) 43 | 44 | if let specifierCell: UITableViewCell = dynamicSpecifier.property(forKey: PSTableCellKey) as? UITableViewCell { 45 | specifierCell.clipsToBounds = shouldHide 46 | 47 | if shouldHide { 48 | if !hiddenSpecifiers.contains(dynamicSpecifier) { 49 | hiddenSpecifiers.append(dynamicSpecifier) 50 | } 51 | return 0 52 | } 53 | 54 | } 55 | } else { 56 | if hiddenSpecifiers.contains(dynamicSpecifier) { 57 | hiddenSpecifiers = hiddenSpecifiers.filter({$0 != dynamicSpecifier}) 58 | } 59 | } 60 | } 61 | } 62 | 63 | return UITableView.automaticDimension 64 | } 65 | 66 | func shouldHideSpecifier(_ specifier: PSSpecifier) -> Bool { 67 | let dynamicSpecifierRule = specifier.property(forKey: "dynamicRule") as! String 68 | let components: [String] = dynamicSpecifierRule.components(separatedBy: ",") 69 | let opposingSpecifier = self.specifier(forID: components.first) 70 | let opposingValue: NSNumber = self.readPreferenceValue(opposingSpecifier) as! NSNumber 71 | let requiredValueString: String = components.last! 72 | let requiredValue = Int(requiredValueString) 73 | 74 | //Hide for all values except one... Useful for list controllers. 75 | if components.count == 3 { 76 | let shouldStayVisible: Bool = components[1] == "s" //s for show, h for hide. 77 | 78 | if shouldStayVisible { 79 | if hiddenSpecifiers.contains(opposingSpecifier!) { 80 | return true 81 | } 82 | 83 | if opposingValue.intValue == requiredValue { 84 | return false 85 | } 86 | 87 | return true 88 | } 89 | } 90 | 91 | //If there's no h or s in the dynamicRule, don't do anything fancy... 92 | return hiddenSpecifiers.contains(opposingSpecifier!) || opposingValue.intValue == requiredValue 93 | } 94 | 95 | func collectDynamicSpecifiersFromArray(_ array: NSArray) { 96 | if !self.dynamicSpecifiers.isEmpty { 97 | self.dynamicSpecifiers.removeAll() 98 | } 99 | 100 | var dynamicSpecifiersArray: [PSSpecifier] = [PSSpecifier]() 101 | 102 | for item in array { 103 | if let item = item as? PSSpecifier { 104 | if let dynamicSpecifierRule = item.property(forKey: "dynamicRule") as? String { 105 | if dynamicSpecifierRule.count > 0 { 106 | dynamicSpecifiersArray.append(item) 107 | } 108 | } 109 | } 110 | } 111 | 112 | let groupedDict = Dictionary(grouping: dynamicSpecifiersArray, by: {($0.property(forKey: "dynamicRule") as! String).components(separatedBy: ",").first!}) 113 | 114 | for key in groupedDict.keys { 115 | let sortedSpecifiers = groupedDict[key]! 116 | dynamicSpecifiers[key] = sortedSpecifiers 117 | } 118 | 119 | self.hasDynamicSpecifiers = (self.dynamicSpecifiers.count > 0) 120 | } 121 | 122 | override func viewDidLoad() { 123 | super.viewDidLoad() 124 | self.table.keyboardDismissMode = .onDrag 125 | 126 | let applyButton = GSRespringButton() 127 | self.navigationItem.rightBarButtonItem = applyButton 128 | } 129 | 130 | override func readPreferenceValue(_ specifier: PSSpecifier!) -> Any! { 131 | var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml 132 | 133 | let plistURL = URL(fileURLWithPath: "/User/Library/Preferences/com.ginsu.\(name)-new.plist") 134 | 135 | guard let plistXML = try? Data(contentsOf: plistURL) else { 136 | return specifier.properties["default"] 137 | } 138 | 139 | guard let plistDict = try! PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String : AnyObject] else { 140 | return specifier.properties["default"] 141 | } 142 | 143 | guard let value = plistDict[specifier.properties["key"] as! String] else { 144 | return specifier.properties["default"] 145 | } 146 | 147 | return value 148 | } 149 | 150 | override func setPreferenceValue(_ value: Any!, specifier: PSSpecifier!) { 151 | var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml 152 | 153 | let plistURL = URL(fileURLWithPath: "/User/Library/Preferences/com.ginsu.\(name)-new.plist") 154 | 155 | guard let plistXML = try? Data(contentsOf: plistURL) else { 156 | return 157 | } 158 | 159 | guard var plistDict = try! PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String : AnyObject] else { 160 | return 161 | } 162 | 163 | plistDict[specifier.properties["key"] as! String] = value! as AnyObject 164 | 165 | do { 166 | let newData = try PropertyListSerialization.data(fromPropertyList: plistDict, format: propertyListFormat, options: 0) 167 | try newData.write(to: plistURL) 168 | } catch { 169 | return 170 | } 171 | 172 | if hasDynamicSpecifiers { 173 | if let specifierID = specifier.property(forKey: PSIDKey) as? String { 174 | let dynamicSpecifier = self.dynamicSpecifiers[specifierID] 175 | 176 | guard dynamicSpecifier != nil else { 177 | return 178 | } 179 | 180 | self.table.reloadData() 181 | } 182 | } 183 | } 184 | 185 | override func viewWillAppear(_ animated: Bool) { 186 | super.viewWillAppear(animated) 187 | 188 | self.reloadSpecifiers() 189 | self.table.reloadData() 190 | } 191 | 192 | override func tableViewStyle() -> UITableView.Style { 193 | if #available(iOS 13.0, *) { 194 | return .insetGrouped 195 | } else { 196 | return .grouped 197 | } 198 | } 199 | 200 | override func _returnKeyPressed(_ arg1: Any!) { 201 | self.view.endEditing(true) 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowser/PBPickerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PBPickerController.swift 3 | // 4 | // 5 | // Created by Noah Little on 12/3/2022. 6 | // 7 | 8 | import UIKit 9 | import Preferences 10 | 11 | class PBPickerController: PSListItemsController { 12 | override func tableViewStyle() -> UITableView.Style { 13 | if #available(iOS 13.0, *) { 14 | return .insetGrouped 15 | } else { 16 | return .grouped 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowser/RootListController.swift: -------------------------------------------------------------------------------- 1 | import Preferences 2 | import protectedbrowserC 3 | import Foundation 4 | 5 | class RootListController: PSListController { 6 | private var name = "protectedbrowser" 7 | 8 | override var specifiers: NSMutableArray? { 9 | get { 10 | if let specifiers = value(forKey: "_specifiers") as? NSMutableArray { 11 | return specifiers 12 | } else { 13 | let specifiers = loadSpecifiers(fromPlistName: "Root", target: self) 14 | setValue(specifiers, forKey: "_specifiers") 15 | return specifiers 16 | } 17 | } 18 | set { 19 | super.specifiers = newValue 20 | } 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | self.table.keyboardDismissMode = .onDrag 26 | 27 | if let icon = UIImage(named: "/Library/PreferenceBundles/\(name).bundle/PrefIcon.png") { 28 | self.navigationItem.titleView = UIImageView(image: icon) 29 | } 30 | 31 | let applyButton = GSRespringButton() 32 | self.navigationItem.rightBarButtonItem = applyButton 33 | } 34 | 35 | override func readPreferenceValue(_ specifier: PSSpecifier!) -> Any! { 36 | var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml 37 | 38 | let plistURL = URL(fileURLWithPath: "/User/Library/Preferences/com.ginsu.\(name)-new.plist") 39 | 40 | guard let plistXML = try? Data(contentsOf: plistURL) else { 41 | return specifier.properties["default"] 42 | } 43 | 44 | guard let plistDict = try! PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String : AnyObject] else { 45 | return specifier.properties["default"] 46 | } 47 | 48 | guard let value = plistDict[specifier.properties["key"] as! String] else { 49 | return specifier.properties["default"] 50 | } 51 | 52 | return value 53 | } 54 | 55 | override func setPreferenceValue(_ value: Any!, specifier: PSSpecifier!) { 56 | var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml 57 | 58 | let plistURL = URL(fileURLWithPath: "/User/Library/Preferences/com.ginsu.\(name)-new.plist") 59 | 60 | guard let plistXML = try? Data(contentsOf: plistURL) else { 61 | return 62 | } 63 | 64 | guard var plistDict = try! PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String : AnyObject] else { 65 | return 66 | } 67 | 68 | plistDict[specifier.properties["key"] as! String] = value! as AnyObject 69 | 70 | do { 71 | let newData = try PropertyListSerialization.data(fromPropertyList: plistDict, format: propertyListFormat, options: 0) 72 | try newData.write(to: plistURL) 73 | } catch { 74 | return 75 | } 76 | } 77 | 78 | override func tableViewStyle() -> UITableView.Style { 79 | if #available(iOS 13.0, *) { 80 | return .insetGrouped 81 | } else { 82 | return .grouped 83 | } 84 | } 85 | 86 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 87 | if (section == 0) { 88 | return GSHeaderView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 200), 89 | twitterHandle: "ginsudev", 90 | developerName: "Ginsu", 91 | tweakName: "ProtectedBrowser", 92 | tweakVersion: "v1.2.4", 93 | email: "njl02@outlook.com", 94 | discordURL: "https://discord.gg/BhdUyCbgkZ", 95 | donateURL: "https://paypal.me/xiaonuoya") 96 | } 97 | 98 | return nil 99 | } 100 | 101 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 102 | return section == 0 ? 150 : 45 103 | } 104 | 105 | override func _returnKeyPressed(_ arg1: Any!) { 106 | self.view.endEditing(true) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowserC/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module protectedbrowserC { 2 | umbrella "." 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowserC/include/protectedbrowser.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | 5 | @interface UIView (Private) 6 | - (UIViewController *)_viewControllerForAncestor; 7 | @end 8 | 9 | @interface PSListController (Private) 10 | - (void)_returnKeyPressed:(id)arg1; 11 | @end 12 | 13 | @interface PSSpecifier (Private) 14 | -(void)performSetterWithValue:(id)value; 15 | -(id)performGetter; 16 | @end 17 | -------------------------------------------------------------------------------- /protectedbrowser/Sources/protectedbrowserC/protectedbrowser.m: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /protectedbrowser/layout/Library/PreferenceLoader/Preferences/protectedbrowser.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entry 6 | 7 | bundle 8 | protectedbrowser 9 | cell 10 | PSLinkCell 11 | detail 12 | protectedbrowser.RootListController 13 | icon 14 | PrefIcon.png 15 | isController 16 | 17 | label 18 | ProtectedBrowser 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------