1 /* Cydia Substrate - Powerful Code Insertion Platform
2 * Copyright (C) 2008-2013 Jay Freeman (saurik)
5 /* GNU Lesser General Public License, Version 3 {{{ */
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.
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.
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/>.
22 #import <CoreFoundation/CoreFoundation.h>
23 #import <Foundation/Foundation.h>
24 #import <CoreGraphics/CGGeometry.h>
25 #import <UIKit/UIKit.h>
28 #include <substrate.h>
30 Class $SafeModeAlertItem;
32 #define UIAlertActionStyleDefault 0
34 @interface UIAlertAction
35 + (UIAlertAction *) actionWithTitle:(NSString *)title style:(NSUInteger)style handler:(void (^)(UIAlertAction *action))handler;
38 @interface UIAlertController : UIViewController
39 - (void) addAction:(UIAlertAction *)action;
40 - (void) setMessage:(NSString *)message;
41 - (void) setTitle:(NSString *)title;
44 @interface SBAlertItem : NSObject {
46 - (UIAlertController *) alertController;
47 - (UIAlertView *) alertSheet;
48 - (void) deactivateForButton;
52 @interface SBAlertItemsController : NSObject {
54 + (SBAlertItemsController *) sharedInstance;
55 - (void) activateAlertItem:(SBAlertItem *)item;
58 @interface SBStatusBarTimeView : UIView {
63 @interface UIApplication (CydiaSubstrate)
64 - (void) applicationOpenURL:(id)url;
67 @interface UIAlertView (CydiaSubstrate)
68 - (void) setForceHorizontalButtonsLayout:(BOOL)force;
69 - (void) setBodyText:(NSString *)body;
70 - (void) setNumberOfRows:(NSInteger)rows;
73 void SafeModeButtonClicked(int button) {
79 if (kCFCoreFoundationVersionNumber >= 700)
80 system("killall backboardd");
82 // XXX: there are better ways of restarting SpringBoard that would actually save state
87 [[UIApplication sharedApplication] applicationOpenURL:[NSURL URLWithString:@"http://cydia.saurik.com/safemode/"]];
92 void SafeModeButtonClicked(id self, int button) {
93 SafeModeButtonClicked(button);
94 if ([self respondsToSelector:@selector(deactivateForButton)])
95 [self deactivateForButton];
100 void SafeModeAlertItem$alertSheet$buttonClicked$(id self, SEL sel, id sheet, int button) {
101 SafeModeButtonClicked(button);
105 void SafeModeAlertItem$alertView$clickedButtonAtIndex$(id self, SEL sel, id sheet, NSInteger button) {
106 SafeModeButtonClicked(button + 1);
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.");
113 #if defined(__ARM_ARCH_7S__) || defined(__arm64__)
114 if ([self respondsToSelector:@selector(alertController)]) {
115 UIAlertController *alert([self alertController]);
117 [alert setTitle:@""];
118 [alert setMessage:text];
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); }]];
127 if (false) %c(UIAlertAction);
130 UIAlertView *sheet([self alertSheet]);
131 [sheet setDelegate:self];
133 [sheet setTitle:@""];
134 [sheet setBodyText:text];
136 [sheet addButtonWithTitle:@"OK"];
137 [sheet addButtonWithTitle:@"Restart"];
138 [sheet addButtonWithTitle:@"Help"];
140 [sheet setNumberOfRows:1];
141 if ([sheet respondsToSelector:@selector(setForceHorizontalButtonsLayout:)])
142 [sheet setForceHorizontalButtonsLayout:YES];
145 void SafeModeAlertItem$performUnlockAction(id self, SEL sel) {
146 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:self];
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)
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);
164 if (%c(SBAlertItemsController) != nil)
165 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:[[[$SafeModeAlertItem alloc] init] autorelease]];
169 // XXX: on iOS 5.0, we really would prefer avoiding
172 - (void) touchesEnded:(id)touches withEvent:(id)event {
174 %orig(touches, event);
178 - (void) mouseDown:(void *)event {
184 - (void) touchesBegan:(void *)touches withEvent:(void *)event {
186 %orig(touches, event);
190 // this fairly complex code came from Grant, to solve the "it Safe Mode"-in-bar bug
192 %hook SBStatusBarDataManager
193 - (void) _updateTimeString {
194 char *_data(&MSHookIvar<char>(self, "_data"));
198 Ivar _itemIsEnabled(object_getInstanceVariable(self, "_itemIsEnabled", NULL));
199 if (_itemIsEnabled == NULL)
202 Ivar _itemIsCloaked(object_getInstanceVariable(self, "_itemIsCloaked", NULL));
203 if (_itemIsCloaked == NULL)
206 size_t enabledOffset(ivar_getOffset(_itemIsEnabled));
207 size_t cloakedOffset(ivar_getOffset(_itemIsCloaked));
208 if (enabledOffset >= cloakedOffset)
211 size_t offset(cloakedOffset - enabledOffset);
212 char *timeString(_data + offset);
213 strcpy(timeString, "Exit Safe Mode");
217 // this /insanely/ complex code came from that parrot guy... omg this is getting bad
219 @interface SBStatusBarStateAggregator : NSObject
220 - (void) _stopTimeItemTimer;
223 %hook SBStatusBarStateAggregator
225 - (void) _updateTimeItems {
226 if ([self respondsToSelector:@selector(_stopTimeItemTimer)])
227 [self _stopTimeItemTimer];
231 - (void) _restartTimeItemTimer {
234 - (void) _resetTimeItemFormatter {
236 if (NSDateFormatter *df = MSHookIvar<NSDateFormatter *>(self, "_timeItemDateFormatter"))
237 [df setDateFormat:@"'Exit' 'Safe' 'Mode'"];
243 static bool alerted_;
245 static void AlertIfNeeded() {
253 %hook SBLockScreenManager
254 - (void) _finishUIUnlockFromSource:(int)source withOptions:(id)options {
260 %hook SBAwayController
261 - (void) _finishUnlockWithSound:(BOOL)sound unlockSource:(int)source isAutoUnlock:(BOOL)is {
267 %hook SBAwayController
268 - (void) _unlockWithSound:(BOOL)sound isAutoUnlock:(BOOL)is unlockSource:(int)source {
273 // iOS 4.3 XXX: check lower versions
274 %hook SBAwayController
275 - (void) _unlockWithSound:(BOOL)sound isAutoUnlock:(BOOL)is unlockType:(int)type {
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
284 %hook SBIconController
285 - (void) showInfoAlertIfNeeded {
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
295 - (int) maxIconColumns {
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]);
315 if ((self = %orig()) != nil) {
316 UIView *&_contentLayer(MSHookIvar<UIView *>(self, "_contentLayer"));
317 UIView *&_contentView(MSHookIvar<UIView *>(self, "_contentView"));
320 if (&_contentLayer != NULL)
321 layer = _contentLayer;
322 else if (&_contentView != NULL)
323 layer = _contentView;
328 [layer setBackgroundColor:[UIColor darkGrayColor]];
332 #define Paper_ "/Library/MobileSubstrate/MobileSafety.png"
334 %hook SBWallpaperImage
340 + (UIImage *) defaultDesktopImage {
341 return [UIImage imageWithContentsOfFile:@Paper_];
344 %hook SBStatusBarTimeView
346 NSString *&_time(MSHookIvar<NSString *>(self, "_time"));
347 CGRect &_textRect(MSHookIvar<CGRect>(self, "_textRect"));
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;
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)
364 %hook BKSApplicationLaunchSettings
365 - (void) setEnvironment:(NSDictionary *)original {
369 NSMutableDictionary *modified([original mutableCopy]);
370 [modified setObject:@"1" forKey:@"_MSSafeMode"];
371 return %orig(modified);
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"
378 const char *dylibs_[] = {
380 "/System/Library/Frameworks",
381 "/System/Library/PrivateFrameworks",
382 "/System/Library/CoreServices",
383 "/System/Library/AccessibilityBundles",
384 "/System/Library/HIDPlugins",
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
392 for (const char **dylib = dylibs_; *dylib != NULL; ++dylib) {
393 size_t length(strlen(*dylib));
394 if (strncmp(path, *dylib, length) != 0)
396 if (path[length] != '/')
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
405 if (access(path, F_OK) == 0)
409 return _dlopen(path, mode);
414 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
416 MSHookFunction(&dlopen, MSHake(dlopen));
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.
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]);