]> git.saurik.com Git - safemode-ios.git/blob - Tweak.xm
Fix OK button on iOS 8 (and maybe other versions).
[safemode-ios.git] / Tweak.xm
1 /* Cydia Substrate - Powerful Code Insertion Platform
2 * Copyright (C) 2008-2013 Jay Freeman (saurik)
3 */
4
5 /* GNU Lesser General Public License, Version 3 {{{ */
6 /*
7 * Substrate is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * Substrate is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Substrate. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 #import <CoreFoundation/CoreFoundation.h>
23 #import <Foundation/Foundation.h>
24 #import <CoreGraphics/CGGeometry.h>
25 #import <UIKit/UIKit.h>
26
27 #include <dlfcn.h>
28 #include <substrate.h>
29
30 Class $SafeModeAlertItem;
31
32 #define UIAlertActionStyleDefault 0
33
34 @interface UIAlertAction
35 + (UIAlertAction *) actionWithTitle:(NSString *)title style:(NSUInteger)style handler:(void (^)(UIAlertAction *action))handler;
36 @end
37
38 @interface UIAlertController : UIViewController
39 - (void) addAction:(UIAlertAction *)action;
40 - (void) setMessage:(NSString *)message;
41 - (void) setTitle:(NSString *)title;
42 @end
43
44 @interface SBAlertItem : NSObject {
45 }
46 - (UIAlertController *) alertController;
47 - (UIAlertView *) alertSheet;
48 - (void) deactivateForButton;
49 - (void) dismiss;
50 @end
51
52 @interface SBAlertItemsController : NSObject {
53 }
54 + (SBAlertItemsController *) sharedInstance;
55 - (void) activateAlertItem:(SBAlertItem *)item;
56 @end
57
58 @interface SBStatusBarTimeView : UIView {
59 }
60 - (id) textFont;
61 @end
62
63 @interface UIApplication (CydiaSubstrate)
64 - (void) applicationOpenURL:(id)url;
65 @end
66
67 @interface UIAlertView (CydiaSubstrate)
68 - (void) setForceHorizontalButtonsLayout:(BOOL)force;
69 - (void) setBodyText:(NSString *)body;
70 - (void) setNumberOfRows:(NSInteger)rows;
71 @end
72
73 void SafeModeButtonClicked(int button) {
74 switch (button) {
75 case 1:
76 break;
77
78 case 2:
79 if (kCFCoreFoundationVersionNumber >= 700)
80 system("killall backboardd");
81 else
82 // XXX: there are better ways of restarting SpringBoard that would actually save state
83 exit(0);
84 break;
85
86 case 3:
87 [[UIApplication sharedApplication] applicationOpenURL:[NSURL URLWithString:@"http://cydia.saurik.com/safemode/"]];
88 break;
89 }
90 }
91
92 void SafeModeButtonClicked(id self, int button) {
93 SafeModeButtonClicked(button);
94 if ([self respondsToSelector:@selector(deactivateForButton)])
95 [self deactivateForButton];
96 else
97 [self dismiss];
98 }
99
100 void SafeModeAlertItem$alertSheet$buttonClicked$(id self, SEL sel, id sheet, int button) {
101 SafeModeButtonClicked(button);
102 [self dismiss];
103 }
104
105 void SafeModeAlertItem$alertView$clickedButtonAtIndex$(id self, SEL sel, id sheet, NSInteger button) {
106 SafeModeButtonClicked(button + 1);
107 [self dismiss];
108 }
109
110 void SafeModeAlertItem$configure$requirePasscodeForActions$(id self, SEL sel, BOOL configure, BOOL require) {
111 NSString *text(@"We apologize for the inconvenience, but SpringBoard has just crashed.\n\nMobileSubstrate /did not/ cause this problem: it has protected you from it.\n\nSpringBoard is now running in Safe Mode. All extensions that support this safety system are disabled.\n\nReboot (or restart SpringBoard) to return to the normal mode. To return to this dialog touch the status bar.\n\nTap \"Help\" below for more tips.");
112
113 #if defined(__ARM_ARCH_7S__) || defined(__arm64__)
114 if ([self respondsToSelector:@selector(alertController)]) {
115 UIAlertController *alert([self alertController]);
116
117 [alert setTitle:@""];
118 [alert setMessage:text];
119
120 [alert addAction:[%c(UIAlertAction) actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { SafeModeButtonClicked(self, 1); }]];
121 [alert addAction:[%c(UIAlertAction) actionWithTitle:@"Restart" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { SafeModeButtonClicked(self, 2); }]];
122 [alert addAction:[%c(UIAlertAction) actionWithTitle:@"Help" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { SafeModeButtonClicked(self, 3); }]];
123
124 return;
125 }
126 #else
127 if (false) %c(UIAlertAction);
128 #endif
129
130 UIAlertView *sheet([self alertSheet]);
131 [sheet setDelegate:self];
132
133 [sheet setTitle:@""];
134 [sheet setBodyText:text];
135
136 [sheet addButtonWithTitle:@"OK"];
137 [sheet addButtonWithTitle:@"Restart"];
138 [sheet addButtonWithTitle:@"Help"];
139
140 [sheet setNumberOfRows:1];
141 if ([sheet respondsToSelector:@selector(setForceHorizontalButtonsLayout:)])
142 [sheet setForceHorizontalButtonsLayout:YES];
143 }
144
145 void SafeModeAlertItem$performUnlockAction(id self, SEL sel) {
146 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:self];
147 }
148
149 static void MSAlert() {
150 if ($SafeModeAlertItem == nil)
151 $SafeModeAlertItem = objc_lookUpClass("SafeModeAlertItem");
152 if ($SafeModeAlertItem == nil) {
153 $SafeModeAlertItem = objc_allocateClassPair(objc_getClass("SBAlertItem"), "SafeModeAlertItem", 0);
154 if ($SafeModeAlertItem == nil)
155 return;
156
157 class_addMethod($SafeModeAlertItem, @selector(alertSheet:buttonClicked:), (IMP) &SafeModeAlertItem$alertSheet$buttonClicked$, "v@:@i");
158 class_addMethod($SafeModeAlertItem, @selector(alertView:clickedButtonAtIndex:), (IMP) &SafeModeAlertItem$alertView$clickedButtonAtIndex$, "v@:@i");
159 class_addMethod($SafeModeAlertItem, @selector(configure:requirePasscodeForActions:), (IMP) &SafeModeAlertItem$configure$requirePasscodeForActions$, "v@:cc");
160 class_addMethod($SafeModeAlertItem, @selector(performUnlockAction), (IMP) SafeModeAlertItem$performUnlockAction, "v@:");
161 objc_registerClassPair($SafeModeAlertItem);
162 }
163
164 if (%c(SBAlertItemsController) != nil)
165 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:[[[$SafeModeAlertItem alloc] init] autorelease]];
166 }
167
168
169 // XXX: on iOS 5.0, we really would prefer avoiding
170
171 %hook SBStatusBar
172 - (void) touchesEnded:(id)touches withEvent:(id)event {
173 MSAlert();
174 %orig(touches, event);
175 } %end
176
177 %hook SBStatusBar
178 - (void) mouseDown:(void *)event {
179 MSAlert();
180 %orig(event);
181 } %end
182
183 %hook UIStatusBar
184 - (void) touchesBegan:(void *)touches withEvent:(void *)event {
185 MSAlert();
186 %orig(touches, event);
187 } %end
188
189
190 // this fairly complex code came from Grant, to solve the "it Safe Mode"-in-bar bug
191
192 %hook SBStatusBarDataManager
193 - (void) _updateTimeString {
194 char *_data(&MSHookIvar<char>(self, "_data"));
195 if (_data == NULL)
196 return;
197
198 Ivar _itemIsEnabled(object_getInstanceVariable(self, "_itemIsEnabled", NULL));
199 if (_itemIsEnabled == NULL)
200 return;
201
202 Ivar _itemIsCloaked(object_getInstanceVariable(self, "_itemIsCloaked", NULL));
203 if (_itemIsCloaked == NULL)
204 return;
205
206 size_t enabledOffset(ivar_getOffset(_itemIsEnabled));
207 size_t cloakedOffset(ivar_getOffset(_itemIsCloaked));
208 if (enabledOffset >= cloakedOffset)
209 return;
210
211 size_t offset(cloakedOffset - enabledOffset);
212 char *timeString(_data + offset);
213 strcpy(timeString, "Exit Safe Mode");
214 } %end
215
216
217 // this /insanely/ complex code came from that parrot guy... omg this is getting bad
218
219 @interface SBStatusBarStateAggregator : NSObject
220 - (void) _stopTimeItemTimer;
221 @end
222
223 %hook SBStatusBarStateAggregator
224
225 - (void) _updateTimeItems {
226 if ([self respondsToSelector:@selector(_stopTimeItemTimer)])
227 [self _stopTimeItemTimer];
228 %orig;
229 }
230
231 - (void) _restartTimeItemTimer {
232 }
233
234 - (void) _resetTimeItemFormatter {
235 %orig;
236 if (NSDateFormatter *df = MSHookIvar<NSDateFormatter *>(self, "_timeItemDateFormatter"))
237 [df setDateFormat:@"'Exit' 'Safe' 'Mode'"];
238 }
239
240 %end
241
242
243 static bool alerted_;
244
245 static void AlertIfNeeded() {
246 if (alerted_)
247 return;
248 alerted_ = true;
249 MSAlert();
250 }
251
252 // iOS 7
253 %hook SBLockScreenManager
254 - (void) _finishUIUnlockFromSource:(int)source withOptions:(id)options {
255 %orig;
256 AlertIfNeeded();
257 } %end
258
259 // iOS 6
260 %hook SBAwayController
261 - (void) _finishUnlockWithSound:(BOOL)sound unlockSource:(int)source isAutoUnlock:(BOOL)is {
262 %orig;
263 AlertIfNeeded();
264 } %end
265
266 // iOS 5
267 %hook SBAwayController
268 - (void) _unlockWithSound:(BOOL)sound isAutoUnlock:(BOOL)is unlockSource:(int)source {
269 %orig;
270 AlertIfNeeded();
271 } %end
272
273 // iOS 4.3 XXX: check lower versions
274 %hook SBAwayController
275 - (void) _unlockWithSound:(BOOL)sound isAutoUnlock:(BOOL)is unlockType:(int)type {
276 %orig;
277 AlertIfNeeded();
278 } %end
279
280 // -[SBIconController showInfoAlertIfNeeded] explains how to drag icons around the iPhone home screen
281 // it used to be shown to users when they unlocked their screen for the first time, and happened every unlock
282 // however, as of iOS 4.3, it got relegated to only appearing once the user installed an app or web clip
283
284 %hook SBIconController
285 - (void) showInfoAlertIfNeeded {
286 AlertIfNeeded();
287 } %end
288
289
290 // the icon state, including crazy configurations like Five Icon Dock, is stored in SpringBoard's defaults
291 // unfortunately, SpringBoard on iOS 2.0 and 2.1 (maybe 2.2 as well) buffer overrun with more than 4 icons
292 // there is a third party package called IconSupport that remedies this, but not everyone is using it yet
293
294 %hook SBButtonBar
295 - (int) maxIconColumns {
296 static int max;
297 if (max == 0) {
298 max = %orig();
299 if (NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults])
300 if (NSDictionary *iconState = [defaults objectForKey:@"iconState"])
301 if (NSDictionary *buttonBar = [iconState objectForKey:@"buttonBar"])
302 if (NSArray *iconMatrix = [buttonBar objectForKey:@"iconMatrix"])
303 if ([iconMatrix count] != 0)
304 if (NSArray *row = [iconMatrix objectAtIndex:0]) {
305 int count([row count]);
306 if (max < count)
307 max = count;
308 }
309 } return max;
310 } %end
311
312
313 %hook SBUIController
314 - (id) init {
315 if ((self = %orig()) != nil) {
316 UIView *&_contentLayer(MSHookIvar<UIView *>(self, "_contentLayer"));
317 UIView *&_contentView(MSHookIvar<UIView *>(self, "_contentView"));
318
319 UIView *layer;
320 if (&_contentLayer != NULL)
321 layer = _contentLayer;
322 else if (&_contentView != NULL)
323 layer = _contentView;
324 else
325 layer = nil;
326
327 if (layer != nil)
328 [layer setBackgroundColor:[UIColor darkGrayColor]];
329 } return self;
330 } %end
331
332 #define Paper_ "/Library/MobileSubstrate/MobileSafety.png"
333
334 %hook SBWallpaperImage
335 + (id) alloc {
336 return nil;
337 } %end
338
339 %hook UIImage
340 + (UIImage *) defaultDesktopImage {
341 return [UIImage imageWithContentsOfFile:@Paper_];
342 } %end
343
344 %hook SBStatusBarTimeView
345 - (void) tile {
346 NSString *&_time(MSHookIvar<NSString *>(self, "_time"));
347 CGRect &_textRect(MSHookIvar<CGRect>(self, "_textRect"));
348 if (_time != nil)
349 [_time release];
350 _time = [@"Exit Safe Mode" retain];
351 id font([self textFont]);
352 CGSize size([_time sizeWithFont:font]);
353 CGRect frame([self frame]);
354 _textRect.size = size;
355 _textRect.origin.x = (frame.size.width - size.width) / 2;
356 _textRect.origin.y = (frame.size.height - size.height) / 2;
357 } %end
358
359
360 // on iOS 6.0, Apple split parts of SpringBoard into a daemon called backboardd, including app launches
361 // in order to allow safe mode to propogate into applications, we need to then tell backboardd here
362 // XXX: (all of this should be replaced, however, with per-process launchd-mediated exception handling)
363
364 %hook BKSApplicationLaunchSettings
365 - (void) setEnvironment:(NSDictionary *)original {
366 if (original == nil)
367 return %orig(nil);
368
369 NSMutableDictionary *modified([original mutableCopy]);
370 [modified setObject:@"1" forKey:@"_MSSafeMode"];
371 return %orig(modified);
372 } %end
373
374
375 // this highly-general hook replaces all previous attempts to protect SpringBoard from spurious code
376 // the main purpose is to protect SpringBoard from non-Substrate "away view plug-ins" and "wee apps"
377
378 const char *dylibs_[] = {
379 "/usr/lib",
380 "/System/Library/Frameworks",
381 "/System/Library/PrivateFrameworks",
382 "/System/Library/CoreServices",
383 "/System/Library/AccessibilityBundles",
384 "/System/Library/HIDPlugins",
385 NULL,
386 };
387
388 MSHook(void *, dlopen, const char *path, int mode) {
389 // we probably don't need this whitelist, but it has the nifty benefit of letting Cycript inject
390 // that said, older versions of iOS (before 3.1) will need a special case due to now shared cache
391
392 for (const char **dylib = dylibs_; *dylib != NULL; ++dylib) {
393 size_t length(strlen(*dylib));
394 if (strncmp(path, *dylib, length) != 0)
395 continue;
396 if (path[length] != '/')
397 continue;
398 goto load;
399 }
400
401 // if the file is not on disk, and isn't already loaded (LC_ID_DYLIB), it is in the shared cache
402 // files loaded from the shared cache are "trusted". ones that don't exist are clearly harmless.
403 // this allows us to load most of the dynamic functionality of SpringBoard without going nuts ;P
404
405 if (access(path, F_OK) == 0)
406 mode |= RTLD_NOLOAD;
407
408 load:
409 return _dlopen(path, mode);
410 }
411
412
413 %ctor {
414 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
415
416 MSHookFunction(&dlopen, MSHake(dlopen));
417
418 // on iOS 6, backboardd is in charge of brightness, and freaks out when SpringBoard restarts :(
419 // the result is that the device is super dark until we attempt to update the brightness here.
420
421 if (kCFCoreFoundationVersionNumber >= 700) {
422 if (void (*GSEventSetBacklightLevel)(float) = reinterpret_cast<void (*)(float)>(dlsym(RTLD_DEFAULT, "GSEventSetBacklightLevel")))
423 if (NSMutableDictionary *defaults = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Library/Preferences/com.apple.springboard.plist", NSHomeDirectory()]])
424 if (NSNumber *level = [defaults objectForKey:@"SBBacklightLevel2"])
425 GSEventSetBacklightLevel([level floatValue]);
426 }
427
428 [pool release];
429 }