@@ -53,6 +53,14 @@ struct ContentView: View {
53
53
Text ( " Select or drag and drop a pairing file to continue. More info: https://docs.sidestore.io/docs/getting-started/pairing-file " )
54
54
}
55
55
}
56
+ Section {
57
+ Button ( " Bypass 3 app limit " ) {
58
+ testBypassAppLimit ( )
59
+ }
60
+ . disabled ( taskRunning)
61
+ } footer: {
62
+ Text ( " Hide free developer apps from installd, so you could install more than 3 apps. You need to apply this for each 3 apps you install or update. " )
63
+ }
56
64
Section {
57
65
Toggle ( " Action Button " , isOn: bindingForMGKeys ( [ " cT44WE1EohiwRzhsZ8xEsw " ] ) )
58
66
. disabled ( requiresVersion ( 17 ) )
@@ -97,12 +105,12 @@ struct ContentView: View {
97
105
Text ( " Only change device model if you're downloading Apple Intelligence models. Face ID may break. " )
98
106
}
99
107
Section {
100
- Button ( " Bypass 3 app limit " ) {
101
- testBypassAppLimit ( )
102
- }
103
- . disabled ( taskRunning )
108
+ let cacheExtra = mobileGestalt [ " CacheExtra " ] as! NSMutableDictionary
109
+ Toggle ( " Become iPadOS " , isOn : bindingForTrollPad ( ) )
110
+ // validate DeviceClass
111
+ . disabled ( cacheExtra [ " +3Uf0Pm5F8Xy7Onyvko0vA " ] as! String != " iPhone " )
104
112
} footer: {
105
- Text ( " Hide free developer apps from installd , so you could install more than 3 apps. You need to apply this for each 3 apps you install or update ." )
113
+ Text ( " Override user interface idiom to iPadOS , so you could use all iPadOS multitasking features on iPhone. Gives you the same capabilities as TrollPad, but may cause some issues ." )
106
114
}
107
115
Section {
108
116
Toggle ( " Reboot after finish restoring " , isOn: $reboot)
@@ -227,7 +235,6 @@ Thanks to:
227
235
set: { enabled in
228
236
if enabled {
229
237
eligibilityData = try ! Data ( contentsOf: Bundle . main. url ( forResource: " eligibility " , withExtension: " plist " ) !)
230
- cacheExtra [ key] = 1
231
238
featureFlagsData = try ! Data ( contentsOf: Bundle . main. url ( forResource: " FeatureFlags_Global " , withExtension: " plist " ) !)
232
239
cacheExtra [ key] = 1
233
240
} else {
@@ -240,6 +247,43 @@ Thanks to:
240
247
)
241
248
}
242
249
250
+ func bindingForTrollPad( ) -> Binding < Bool > {
251
+ // We're going to overwrite DeviceClassNumber but we can't do it via CacheExtra, so we need to do it via CacheData instead
252
+ // However, CacheData is still a black box, as nobody has yet to document this data, so we're leaving a hardcoded offset for now
253
+ let valueOffset = 0x2e0
254
+ let cacheData = mobileGestalt [ " CacheData " ] as! NSMutableData
255
+ //print("Read value from \(cacheData.mutableBytes.load(fromByteOffset: valueOffset, as: Int.self))")
256
+
257
+ let cacheExtra = mobileGestalt [ " CacheExtra " ] as! NSMutableDictionary
258
+ let keys = [
259
+ " uKc7FPnEO++lVhHWHFlGbQ " , // ipad
260
+ " mG0AnH/Vy1veoqoLRAIgTA " , // MedusaFloatingLiveAppCapability
261
+ " UCG5MkVahJxG1YULbbd5Bg " , // MedusaOverlayAppCapability
262
+ " ZYqko/XM5zD3XBfN5RmaXA " , // MedusaPinnedAppCapability
263
+ " nVh/gwNpy7Jv1NOk00CMrw " , // MedusaPIPCapability,
264
+ " qeaj75wk3HF4DwQ8qbIi7g " , // DeviceSupportsEnhancedMultitasking
265
+ ]
266
+ return Binding (
267
+ get: {
268
+ if let value = cacheExtra [ keys. first!] as? Int ? {
269
+ return value == 1
270
+ }
271
+ return false
272
+ } ,
273
+ set: { enabled in
274
+ cacheData. mutableBytes. storeBytes ( of: enabled ? 3 : 1 , toByteOffset: valueOffset, as: Int . self)
275
+ for key in keys {
276
+ if enabled {
277
+ cacheExtra [ key] = 1
278
+ } else {
279
+ // just remove the key as it will be pulled from device tree if missing
280
+ cacheExtra. removeObject ( forKey: key)
281
+ }
282
+ }
283
+ }
284
+ )
285
+ }
286
+
243
287
func bindingForMGKeys< T: Equatable > ( _ keys: [ String ] , type: T . Type = Int . self, defaultValue: T ? = 0 , enableValue: T ? = 1 ) -> Binding < Bool > {
244
288
let cacheExtra = mobileGestalt [ " CacheExtra " ] as! NSMutableDictionary
245
289
return Binding (
0 commit comments