]> git.saurik.com Git - safemode-ios.git/blob - Tweak.xm
Compile for ARM64 (using Xcode 5 for armv6 hacks).
[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 <substrate.h>
28
29 Class $SafeModeAlertItem;
30
31 @interface SBAlertItem : NSObject {
32 }
33 - (UIAlertView *) alertSheet;
34 - (void) dismiss;
35 @end
36
37 @interface SBAlertItemsController : NSObject {
38 }
39 + (SBAlertItemsController *) sharedInstance;
40 - (void) activateAlertItem:(SBAlertItem *)item;
41 @end
42
43 @interface SBStatusBarTimeView : UIView {
44 }
45 - (id) textFont;
46 @end
47
48 @interface UIApplication (CydiaSubstrate)
49 - (void) applicationOpenURL:(id)url;
50 @end
51
52 @interface UIAlertView (CydiaSubstrate)
53 - (void) setForceHorizontalButtonsLayout:(BOOL)force;
54 - (void) setBodyText:(NSString *)body;
55 - (void) setNumberOfRows:(NSInteger)rows;
56 @end
57
58 void SafeModeAlertItem$alertSheet$buttonClicked$(id self, SEL sel, id sheet, int button) {
59 switch (button) {
60 case 1:
61 break;
62
63 case 2:
64 if (kCFCoreFoundationVersionNumber >= 700)
65 system("killall backboardd");
66 else
67 // XXX: there are better ways of restarting SpringBoard that would actually save state
68 exit(0);
69 break;
70
71 case 3:
72 [[UIApplication sharedApplication] applicationOpenURL:[NSURL URLWithString:@"http://cydia.saurik.com/safemode/"]];
73 break;
74 }
75
76 [self dismiss];
77 }
78
79 void SafeModeAlertItem$configure$requirePasscodeForActions$(id self, SEL sel, BOOL configure, BOOL require) {
80 UIAlertView *sheet([self alertSheet]);
81
82 [sheet setDelegate:self];
83 [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."];
84 [sheet addButtonWithTitle:@"OK"];
85 [sheet addButtonWithTitle:@"Restart"];
86 [sheet addButtonWithTitle:@"Help"];
87 [sheet setNumberOfRows:1];
88
89 if ([sheet respondsToSelector:@selector(setForceHorizontalButtonsLayout:)])
90 [sheet setForceHorizontalButtonsLayout:YES];
91 }
92
93 void SafeModeAlertItem$performUnlockAction(id self, SEL sel) {
94 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:self];
95 }
96
97 static void MSAlert() {
98 if ($SafeModeAlertItem == nil)
99 $SafeModeAlertItem = objc_lookUpClass("SafeModeAlertItem");
100 if ($SafeModeAlertItem == nil) {
101 $SafeModeAlertItem = objc_allocateClassPair(objc_getClass("SBAlertItem"), "SafeModeAlertItem", 0);
102 if ($SafeModeAlertItem == nil)
103 return;
104
105 class_addMethod($SafeModeAlertItem, @selector(alertSheet:buttonClicked:), (IMP) &SafeModeAlertItem$alertSheet$buttonClicked$, "v@:@i");
106 class_addMethod($SafeModeAlertItem, @selector(configure:requirePasscodeForActions:), (IMP) &SafeModeAlertItem$configure$requirePasscodeForActions$, "v@:cc");
107 class_addMethod($SafeModeAlertItem, @selector(performUnlockAction), (IMP) SafeModeAlertItem$performUnlockAction, "v@:");
108 objc_registerClassPair($SafeModeAlertItem);
109 }
110
111 if (%c(SBAlertItemsController) != nil)
112 [[%c(SBAlertItemsController) sharedInstance] activateAlertItem:[[[$SafeModeAlertItem alloc] init] autorelease]];
113 }
114
115
116 // XXX: on iOS 5.0, we really would prefer avoiding
117
118 %hook SBStatusBar
119 - (void) touchesEnded:(id)touches withEvent:(id)event {
120 MSAlert();
121 %orig(touches, event);
122 } %end
123
124 %hook SBStatusBar
125 - (void) mouseDown:(void *)event {
126 MSAlert();
127 %orig(event);
128 } %end
129
130 %hook UIStatusBar
131 - (void) touchesBegan:(void *)touches withEvent:(void *)event {
132 MSAlert();
133 %orig(touches, event);
134 } %end
135
136
137 // this fairly complex code came from Grant, to solve the "it Safe Mode"-in-bar bug
138
139 %hook SBStatusBarDataManager
140 - (void) _updateTimeString {
141 char *_data(&MSHookIvar<char>(self, "_data"));
142 if (_data == NULL)
143 return;
144
145 Ivar _itemIsEnabled(object_getInstanceVariable(self, "_itemIsEnabled", NULL));
146 if (_itemIsEnabled == NULL)
147 return;
148
149 Ivar _itemIsCloaked(object_getInstanceVariable(self, "_itemIsCloaked", NULL));
150 if (_itemIsCloaked == NULL)
151 return;
152
153 size_t enabledOffset(ivar_getOffset(_itemIsEnabled));
154 size_t cloakedOffset(ivar_getOffset(_itemIsCloaked));
155 if (enabledOffset >= cloakedOffset)
156 return;
157
158 size_t offset(cloakedOffset - enabledOffset);
159 char *timeString(_data + offset);
160 strcpy(timeString, "Exit Safe Mode");
161 } %end
162
163
164 static bool alerted_;
165
166 static void AlertIfNeeded() {
167 if (alerted_)
168 return;
169 alerted_ = true;
170 MSAlert();
171 }
172
173
174 // on iOS 4.3 and above we can use this advertisement, which seems to check every time the user unlocks
175 // XXX: verify that this still works on iOS 5.0
176
177 %hook AAAccountManager
178 + (void) showMobileMeOfferIfNecessary {
179 AlertIfNeeded();
180 } %end
181
182
183 // -[SBIconController showInfoAlertIfNeeded] explains how to drag icons around the iPhone home screen
184 // it used to be shown to users when they unlocked their screen for the first time, and happened every unlock
185 // however, as of iOS 4.3, it got relegated to only appearing once the user installed an app or web clip
186
187 %hook SBIconController
188 - (void) showInfoAlertIfNeeded {
189 AlertIfNeeded();
190 } %end
191
192
193 // the icon state, including crazy configurations like Five Icon Dock, is stored in SpringBoard's defaults
194 // unfortunately, SpringBoard on iOS 2.0 and 2.1 (maybe 2.2 as well) buffer overrun with more than 4 icons
195 // there is a third party package called IconSupport that remedies this, but not everyone is using it yet
196
197 %hook SBButtonBar
198 - (int) maxIconColumns {
199 static int max;
200 if (max == 0) {
201 max = %orig();
202 if (NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults])
203 if (NSDictionary *iconState = [defaults objectForKey:@"iconState"])
204 if (NSDictionary *buttonBar = [iconState objectForKey:@"buttonBar"])
205 if (NSArray *iconMatrix = [buttonBar objectForKey:@"iconMatrix"])
206 if ([iconMatrix count] != 0)
207 if (NSArray *row = [iconMatrix objectAtIndex:0]) {
208 int count([row count]);
209 if (max < count)
210 max = count;
211 }
212 } return max;
213 } %end
214
215
216 %hook SBUIController
217 - (id) init {
218 if ((self = %orig()) != nil) {
219 UIView *&_contentLayer(MSHookIvar<UIView *>(self, "_contentLayer"));
220 UIView *&_contentView(MSHookIvar<UIView *>(self, "_contentView"));
221
222 UIView *layer;
223 if (&_contentLayer != NULL)
224 layer = _contentLayer;
225 else if (&_contentView != NULL)
226 layer = _contentView;
227 else
228 layer = nil;
229
230 if (layer != nil)
231 [layer setBackgroundColor:[UIColor darkGrayColor]];
232 } return self;
233 } %end
234
235 #define Paper_ "/Library/MobileSubstrate/MobileSafety.png"
236
237 %hook UIImage
238 + (UIImage *) defaultDesktopImage {
239 return [UIImage imageWithContentsOfFile:@Paper_];
240 } %end
241
242 %hook SBStatusBarTimeView
243 - (void) tile {
244 NSString *&_time(MSHookIvar<NSString *>(self, "_time"));
245 CGRect &_textRect(MSHookIvar<CGRect>(self, "_textRect"));
246 if (_time != nil)
247 [_time release];
248 _time = [@"Exit Safe Mode" retain];
249 id font([self textFont]);
250 CGSize size([_time sizeWithFont:font]);
251 CGRect frame([self frame]);
252 _textRect.size = size;
253 _textRect.origin.x = (frame.size.width - size.width) / 2;
254 _textRect.origin.y = (frame.size.height - size.height) / 2;
255 } %end
256
257
258 // notification widgets ("wee apps" or "bulletin board sections") are capable of crashing SpringBoard
259 // unfortunately, which ones are in use are stored in SpringBoard's defaults, so we need to turn them off
260
261 %hook BBSectionInfo
262 - (BOOL) showsInNotificationCenter {
263 return NO;
264 } %end
265
266
267 // on iOS 6.0, Apple split parts of SpringBoard into a daemon called backboardd, including app launches
268 // in order to allow safe mode to propogate into applications, we need to then tell backboardd here
269 // XXX: (all of this should be replaced, however, with per-process launchd-mediated exception handling)
270
271 %hook BKSApplicationLaunchSettings
272 - (void) setEnvironment:(NSDictionary *)original {
273 if (original == nil)
274 return %orig(nil);
275
276 NSMutableDictionary *modified([original mutableCopy]);
277 [modified setObject:@"1" forKey:@"_MSSafeMode"];
278 return %orig(modified);
279 } %end
280
281 %ctor {
282 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
283
284 // on iOS 6, backboardd is in charge of brightness, and freaks out when SpringBoard restarts :(
285 // the result is that the device is super dark until we attempt to update the brightness here.
286
287 if (kCFCoreFoundationVersionNumber >= 700) {
288 if (void (*GSEventSetBacklightLevel)(float) = reinterpret_cast<void (*)(float)>(dlsym(RTLD_DEFAULT, "GSEventSetBacklightLevel")))
289 if (NSMutableDictionary *defaults = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Library/Preferences/com.apple.springboard.plist", NSHomeDirectory()]])
290 if (NSNumber *level = [defaults objectForKey:@"SBBacklightLevel2"])
291 GSEventSetBacklightLevel([level floatValue]);
292 }
293
294 [pool release];
295 }