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