├── .DS_Store ├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── ProtectedApp.plist ├── README.md ├── Tweak.xm ├── control ├── make.sh └── packages ├── ProtectedApp.dylib ├── rootful └── com.tien0246.protectedapp_1.0.0_iphoneos-arm.deb └── rootless └── com.tien0246.protectedapp_1.0.0_iphoneos-arm64.deb /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tien0246/ProtectedApp/c1e18ca75edc8977afc21f7c8d6759c993a71e83/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.theos 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 tien0246 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ARCHS = arm64 arm64e 2 | DEBUG = 0 3 | FINALPACKAGE = 1 4 | FOR_RELEASE = 1 5 | IGNORE_WARNINGS = 1 6 | GO_EASY_ON_ME = 1 7 | 8 | # THEOS_DEVICE_IP = 127.0.0.1 9 | # THEOS_DEVICE_PORT = 2222 10 | THEOS_DEVICE_IP = 192.168.1.5 11 | THEOS_DEVICE_PORT = 22 12 | 13 | TARGET := iphone:clang:latest:14.0 14 | # INSTALL_TARGET_PROCESSES = Messenger 15 | 16 | ifeq ($(THEOS_PACKAGE_SCHEME),rootless) 17 | THEOS_PACKAGE_DIR = packages/rootless 18 | else 19 | THEOS_PACKAGE_DIR = packages/rootful 20 | endif 21 | 22 | include $(THEOS)/makefiles/common.mk 23 | 24 | TWEAK_NAME = ProtectedApp 25 | 26 | $(TWEAK_NAME)_CFLAGS = -fobjc-arc -fvisibility=hidden 27 | 28 | $(TWEAK_NAME)_CCFLAGS = -std=c++11 -fno-rtti -fno-exceptions -DNDEBUG -fno-objc-arc -O2 29 | 30 | ${TWEAK_NAME}_FILES = Tweak.xm 31 | 32 | ${TWEAK_NAME}_CFLAGS = -fobjc-arc 33 | 34 | $(TWEAK_NAME)_FRAMEWORKS = UIKit Foundation 35 | 36 | include $(THEOS_MAKE_PATH)/tweak.mk 37 | 38 | after-install:: 39 | install.exec "killall -9 Messenger" 40 | 41 | clean:: 42 | rm -rf .theos 43 | -------------------------------------------------------------------------------- /ProtectedApp.plist: -------------------------------------------------------------------------------- 1 | { 2 | Filter = { 3 | Bundles = ( 4 | "com.apple.UIKit", 5 | ); 6 | }; 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProtectedApp 2 | Protected Your App 3 | 4 | 3 fingers twice to open settings 5 | With jailbreak: install via igamegod to enable app you want 6 | -------------------------------------------------------------------------------- /Tweak.xm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface ProtectedApp : NSObject 4 | 5 | @property (nonatomic, strong) UIWindow *lockWindow; 6 | @property (nonatomic, strong) NSMutableString *enteredCode; 7 | @property (nonatomic, assign) NSInteger failedAttempts; 8 | @property (nonatomic, strong) NSDate *lockoutEndTime; 9 | @property (nonatomic, strong) NSDate *lastBackgroundTime; 10 | @property (nonatomic, assign) BOOL isLockScreenPresented; 11 | @property (nonatomic, assign) NSTimeInterval timeoutInterval; 12 | 13 | + (instancetype)sharedInstance; 14 | - (void)presentLockScreenIfNeeded; 15 | - (void)setupLockWindow; 16 | - (void)handleTripleTap; 17 | - (void)showLockScreen; 18 | - (void)numberButtonTapped:(UIButton *)sender; 19 | - (void)checkPasscode; 20 | - (void)resetPasscode; 21 | - (BOOL)isLockedOut; 22 | - (void)applyLockout; 23 | - (void)showLockoutMessage; 24 | - (void)initializePasscodeFileIfNeeded; 25 | - (NSString *)readPasscodeFromDefaults; 26 | - (void)savePasscodeToDefaults:(NSString *)passcode; 27 | - (void)loadLockoutState; 28 | - (void)saveLockoutState; 29 | - (BOOL)isCurrentPasscodeValid:(NSString *)currentPasscode; 30 | - (BOOL)areNewPasscodesValid:(NSString *)newPasscode confirmPasscode:(NSString *)confirmPasscode; 31 | - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message; 32 | - (void)handleWillResignActive; 33 | - (void)handleWillEnterForeground; 34 | 35 | @end 36 | 37 | @implementation ProtectedApp 38 | 39 | + (instancetype)sharedInstance { 40 | static ProtectedApp *sharedInstance = nil; 41 | static dispatch_once_t onceToken; 42 | dispatch_once(&onceToken, ^{ 43 | sharedInstance = [[self alloc] init]; 44 | [[NSNotificationCenter defaultCenter] addObserver:sharedInstance 45 | selector:@selector(handleWillResignActive) 46 | name:UIApplicationWillResignActiveNotification 47 | object:nil]; 48 | 49 | [[NSNotificationCenter defaultCenter] addObserver:sharedInstance 50 | selector:@selector(handleWillResignActive) 51 | name:UIApplicationDidEnterBackgroundNotification 52 | object:nil]; 53 | 54 | [[NSNotificationCenter defaultCenter] addObserver:sharedInstance 55 | selector:@selector(handleWillResignActive) 56 | name:UIApplicationWillTerminateNotification 57 | object:nil]; 58 | 59 | [[NSNotificationCenter defaultCenter] addObserver:sharedInstance 60 | selector:@selector(handleWillEnterForeground) 61 | name:UIApplicationDidBecomeActiveNotification 62 | object:nil]; 63 | 64 | [[NSNotificationCenter defaultCenter] addObserver:sharedInstance 65 | selector:@selector(handleWillEnterForeground) 66 | name:UIApplicationWillEnterForegroundNotification 67 | object:nil]; 68 | 69 | [sharedInstance initializePasscodeFileIfNeeded]; 70 | [sharedInstance loadLockoutState]; 71 | 72 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 73 | sharedInstance.timeoutInterval = [defaults doubleForKey:@"timeoutInterval"]; 74 | if (sharedInstance.timeoutInterval <= 0) { 75 | sharedInstance.timeoutInterval = 15.0; 76 | } 77 | }); 78 | return sharedInstance; 79 | } 80 | 81 | 82 | - (void)handleWillResignActive { 83 | if (!self.lockWindow) self.lastBackgroundTime = [NSDate date]; 84 | [self setupLockWindow]; 85 | } 86 | 87 | - (void)handleWillEnterForeground { 88 | if (self.lastBackgroundTime) { 89 | NSTimeInterval timeInBackground = [[NSDate date] timeIntervalSinceDate:self.lastBackgroundTime]; 90 | if (timeInBackground > self.timeoutInterval) { 91 | [self presentLockScreenIfNeeded]; 92 | } else { 93 | if (self.lockWindow) { 94 | self.lockWindow.hidden = YES; 95 | self.lockWindow = nil; 96 | self.isLockScreenPresented = NO; 97 | } 98 | } 99 | } 100 | } 101 | 102 | 103 | - (void)presentLockScreenIfNeeded { 104 | if (self.isLockScreenPresented) return; 105 | 106 | if (!self.lockWindow || self.lockWindow.hidden) { 107 | [self setupLockWindow]; 108 | } 109 | 110 | if ([self isLockedOut]) { 111 | [self showLockoutMessage]; 112 | return; 113 | } 114 | 115 | [self showLockScreen]; 116 | self.isLockScreenPresented = YES; 117 | } 118 | 119 | 120 | - (void)setupLockWindow { 121 | if (!self.lockWindow) { 122 | self.lockWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 123 | self.lockWindow.backgroundColor = [UIColor clearColor]; 124 | self.lockWindow.windowLevel = UIWindowLevelAlert + 99; 125 | 126 | UIViewController *lockViewController = [[UIViewController alloc] init]; 127 | 128 | UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 129 | UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 130 | blurEffectView.frame = lockViewController.view.bounds; 131 | blurEffectView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 132 | [lockViewController.view addSubview:blurEffectView]; 133 | 134 | self.lockWindow.rootViewController = lockViewController; 135 | [self.lockWindow makeKeyAndVisible]; 136 | 137 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTripleTap)]; 138 | tap.numberOfTapsRequired = 2; 139 | tap.numberOfTouchesRequired = 3; 140 | [self.lockWindow addGestureRecognizer:tap]; 141 | } 142 | } 143 | 144 | - (void)handleTripleTap { 145 | if ([self isLockedOut]) { 146 | [self showLockoutMessage]; 147 | return; 148 | } 149 | 150 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Options" 151 | message:nil 152 | preferredStyle:UIAlertControllerStyleAlert]; 153 | 154 | UIAlertAction *changePasscodeAction = [UIAlertAction actionWithTitle:@"Change Passcode" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 155 | [self showChangePasscodeAlert]; 156 | }]; 157 | 158 | UIAlertAction *changeTimeoutAction = [UIAlertAction actionWithTitle:@"Change Timeout Interval" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 159 | [self showChangeTimeoutAlert]; 160 | }]; 161 | 162 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; 163 | 164 | [alert addAction:changePasscodeAction]; 165 | [alert addAction:changeTimeoutAction]; 166 | [alert addAction:cancelAction]; 167 | 168 | [self.lockWindow.rootViewController presentViewController:alert animated:YES completion:nil]; 169 | } 170 | 171 | - (void)showChangePasscodeAlert { 172 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Change Passcode" 173 | message:nil 174 | preferredStyle:UIAlertControllerStyleAlert]; 175 | 176 | [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 177 | textField.placeholder = @"Enter current passcode"; 178 | textField.secureTextEntry = YES; 179 | textField.keyboardType = UIKeyboardTypeNumberPad; 180 | }]; 181 | 182 | [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 183 | textField.placeholder = @"Enter new passcode"; 184 | textField.secureTextEntry = YES; 185 | textField.keyboardType = UIKeyboardTypeNumberPad; 186 | }]; 187 | 188 | [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 189 | textField.placeholder = @"Confirm new passcode"; 190 | textField.secureTextEntry = YES; 191 | textField.keyboardType = UIKeyboardTypeNumberPad; 192 | }]; 193 | 194 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; 195 | UIAlertAction *changeAction = [UIAlertAction actionWithTitle:@"Change" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 196 | UITextField *currentPasscodeField = alert.textFields[0]; 197 | UITextField *newPasscodeField = alert.textFields[1]; 198 | UITextField *confirmPasscodeField = alert.textFields[2]; 199 | 200 | NSString *currentPasscode = currentPasscodeField.text; 201 | NSString *newPasscode = newPasscodeField.text; 202 | NSString *confirmPasscode = confirmPasscodeField.text; 203 | 204 | if ([self isCurrentPasscodeValid:currentPasscode] && [self areNewPasscodesValid:newPasscode confirmPasscode:confirmPasscode]) { 205 | self.failedAttempts = 0; 206 | [self saveLockoutState]; 207 | [self savePasscodeToDefaults:newPasscode]; 208 | [self showAlertWithTitle:@"Success" message:@"Passcode has been changed."]; 209 | } else { 210 | self.failedAttempts++; 211 | if (self.failedAttempts >= 5) { 212 | [self applyLockout]; 213 | [self showLockoutMessage]; 214 | } else { 215 | [self showAlertWithTitle:@"Error" message:@"Invalid passcode or mismatch. Passcode must be 4 digits."]; 216 | } 217 | } 218 | }]; 219 | 220 | [alert addAction:cancelAction]; 221 | [alert addAction:changeAction]; 222 | 223 | [self.lockWindow.rootViewController presentViewController:alert animated:YES completion:nil]; 224 | } 225 | 226 | - (void)showChangeTimeoutAlert { 227 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Change Timeout Interval" 228 | message:@"Enter the new timeout interval in seconds:" 229 | preferredStyle:UIAlertControllerStyleAlert]; 230 | 231 | [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { 232 | textField.placeholder = @"Timeout interval in seconds"; 233 | textField.keyboardType = UIKeyboardTypeNumberPad; 234 | }]; 235 | 236 | UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; 237 | UIAlertAction *changeAction = [UIAlertAction actionWithTitle:@"Change" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { 238 | UITextField *timeoutField = alert.textFields.firstObject; 239 | NSTimeInterval newTimeoutInterval = [timeoutField.text doubleValue]; 240 | 241 | if (newTimeoutInterval > 0) { 242 | self.timeoutInterval = newTimeoutInterval; 243 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 244 | [defaults setDouble:newTimeoutInterval forKey:@"timeoutInterval"]; 245 | [defaults synchronize]; 246 | [self showAlertWithTitle:@"Success" message:@"Timeout interval has been changed."]; 247 | } else { 248 | [self showAlertWithTitle:@"Error" message:@"Invalid timeout interval."]; 249 | } 250 | }]; 251 | 252 | [alert addAction:cancelAction]; 253 | [alert addAction:changeAction]; 254 | 255 | [self.lockWindow.rootViewController presentViewController:alert animated:YES completion:nil]; 256 | } 257 | 258 | - (BOOL)isCurrentPasscodeValid:(NSString *)currentPasscode { 259 | NSString *savedPasscode = [self readPasscodeFromDefaults]; 260 | return [currentPasscode isEqualToString:savedPasscode]; 261 | } 262 | 263 | - (BOOL)areNewPasscodesValid:(NSString *)newPasscode confirmPasscode:(NSString *)confirmPasscode { 264 | NSCharacterSet *nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; 265 | BOOL isNumeric = ([newPasscode rangeOfCharacterFromSet:nonDigitCharacterSet].location == NSNotFound); 266 | return isNumeric && newPasscode.length == 4 && [newPasscode isEqualToString:confirmPasscode]; 267 | } 268 | 269 | - (void)showAlertWithTitle:(NSString *)title message:(NSString *)message { 270 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:title 271 | message:message 272 | preferredStyle:UIAlertControllerStyleAlert]; 273 | UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]; 274 | [alert addAction:okAction]; 275 | [self.lockWindow.rootViewController presentViewController:alert animated:YES completion:nil]; 276 | } 277 | 278 | - (void)showLockScreen { 279 | self.enteredCode = [NSMutableString string]; 280 | 281 | UIViewController *lockViewController = self.lockWindow.rootViewController; 282 | 283 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 284 | CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; 285 | 286 | CGFloat buttonWidth = screenWidth / 5; 287 | CGFloat buttonHeight = buttonWidth; 288 | 289 | CGFloat totalWidth = buttonWidth * 3 + 40; 290 | CGFloat totalHeight = buttonHeight * 4 + 60; 291 | 292 | CGFloat centerX = screenWidth / 2; 293 | CGFloat startY = (screenHeight - totalHeight - 150) / 2; 294 | 295 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 220, 50)]; 296 | label.center = CGPointMake(centerX, startY); 297 | label.text = @"Enter Passcode"; 298 | label.textColor = [UIColor whiteColor]; 299 | label.textAlignment = NSTextAlignmentCenter; 300 | [lockViewController.view addSubview:label]; 301 | 302 | UIView *codeInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 50)]; 303 | codeInputView.center = CGPointMake(centerX, CGRectGetMaxY(label.frame) + 40); 304 | codeInputView.tag = 100; 305 | [lockViewController.view addSubview:codeInputView]; 306 | 307 | CGFloat dotSize = 20; 308 | CGFloat space = (codeInputView.bounds.size.width - 4 * dotSize) / 3; 309 | 310 | for (int i = 0; i < 4; i++) { 311 | UIView *dotView = [[UIView alloc] initWithFrame:CGRectMake(i * (dotSize + space), 0, dotSize, dotSize)]; 312 | dotView.layer.cornerRadius = dotSize / 2; 313 | dotView.layer.borderColor = [[UIColor whiteColor] CGColor]; 314 | dotView.layer.borderWidth = 1.0; 315 | dotView.backgroundColor = [UIColor clearColor]; 316 | dotView.layer.masksToBounds = YES; 317 | dotView.tag = i + 1; 318 | [codeInputView addSubview:dotView]; 319 | } 320 | 321 | startY = CGRectGetMaxY(codeInputView.frame) + 40; 322 | 323 | NSArray *buttonTitles = @[@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"0"]; 324 | 325 | for (int i = 0; i < 9; i++) { 326 | UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; 327 | button.frame = CGRectMake(centerX - totalWidth / 2 + (i % 3) * (buttonWidth + 20), 328 | startY + (i / 3) * (buttonHeight + 20), 329 | buttonWidth, buttonHeight); 330 | [button setTitle:buttonTitles[i] forState:UIControlStateNormal]; 331 | button.titleLabel.font = [UIFont systemFontOfSize:buttonHeight / 2]; 332 | button.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.1]; 333 | button.layer.cornerRadius = buttonWidth / 2; 334 | button.layer.masksToBounds = YES; 335 | [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 336 | [button addTarget:self action:@selector(numberButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 337 | button.tag = [buttonTitles[i] intValue]; 338 | [lockViewController.view addSubview:button]; 339 | } 340 | 341 | UIButton *zeroButton = [UIButton buttonWithType:UIButtonTypeSystem]; 342 | zeroButton.frame = CGRectMake(centerX - buttonWidth / 2, 343 | startY + 3 * (buttonHeight + 20), 344 | buttonWidth, buttonHeight); 345 | [zeroButton setTitle:@"0" forState:UIControlStateNormal]; 346 | zeroButton.titleLabel.font = [UIFont systemFontOfSize:buttonHeight / 2]; 347 | zeroButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.1]; 348 | zeroButton.layer.cornerRadius = buttonWidth / 2; 349 | zeroButton.layer.masksToBounds = YES; 350 | [zeroButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 351 | [zeroButton addTarget:self action:@selector(numberButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; 352 | zeroButton.tag = 0; 353 | [lockViewController.view addSubview:zeroButton]; 354 | } 355 | 356 | - (void)numberButtonTapped:(UIButton *)sender { 357 | if (self.enteredCode.length < 4) { 358 | [self.enteredCode appendString:[NSString stringWithFormat:@"%ld", (long)sender.tag]]; 359 | 360 | UIView *codeInputView = [self.lockWindow.rootViewController.view viewWithTag:100]; 361 | UIView *dotView = [codeInputView viewWithTag:self.enteredCode.length]; 362 | dotView.backgroundColor = [UIColor whiteColor]; 363 | 364 | if (self.enteredCode.length == 4) { 365 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 366 | [self checkPasscode]; 367 | }); 368 | } 369 | } 370 | } 371 | 372 | - (void)checkPasscode { 373 | NSString *correctPasscode = [self readPasscodeFromDefaults]; 374 | if ([self.enteredCode isEqualToString:correctPasscode]) { 375 | self.failedAttempts = 0; 376 | [self saveLockoutState]; 377 | [self.lockWindow resignKeyWindow]; 378 | self.lockWindow.hidden = YES; 379 | self.lockWindow = nil; 380 | self.isLockScreenPresented = NO; 381 | } else { 382 | self.failedAttempts++; 383 | if (self.failedAttempts >= 5) { 384 | [self applyLockout]; 385 | } else { 386 | [self resetPasscode]; 387 | } 388 | } 389 | } 390 | 391 | 392 | - (void)resetPasscode { 393 | [self.enteredCode setString:@""]; 394 | 395 | UIView *codeInputView = [self.lockWindow.rootViewController.view viewWithTag:100]; 396 | for (int i = 1; i <= 4; i++) { 397 | UIView *dotView = [codeInputView viewWithTag:i]; 398 | dotView.backgroundColor = [UIColor clearColor]; 399 | dotView.layer.borderColor = [[UIColor whiteColor] CGColor]; 400 | dotView.layer.borderWidth = 1.0; 401 | } 402 | } 403 | 404 | - (BOOL)isLockedOut { 405 | if (!self.lockoutEndTime) return NO; 406 | return [[NSDate date] compare:self.lockoutEndTime] == NSOrderedAscending; 407 | } 408 | 409 | - (void)applyLockout { 410 | NSInteger lockoutMinutes = pow(2, self.failedAttempts - 5); 411 | self.lockoutEndTime = [[NSDate date] dateByAddingTimeInterval:lockoutMinutes * 60 + 1]; 412 | [self saveLockoutState]; 413 | [self showLockoutMessage]; 414 | } 415 | 416 | - (void)showLockoutMessage { 417 | NSTimeInterval timeRemaining = [self.lockoutEndTime timeIntervalSinceNow]; 418 | 419 | NSInteger seconds = (NSInteger)timeRemaining % 60; 420 | NSInteger minutes = ((NSInteger)timeRemaining / 60) % 60; 421 | NSInteger hours = ((NSInteger)timeRemaining / 3600) % 24; 422 | NSInteger days = ((NSInteger)timeRemaining / (3600 * 24)) % 30; 423 | NSInteger months = ((NSInteger)timeRemaining / (3600 * 24 * 30)) % 12; 424 | NSInteger years = (NSInteger)timeRemaining / (3600 * 24 * 365); 425 | 426 | NSMutableArray *timeComponents = [NSMutableArray array]; 427 | 428 | if (years > 0) { 429 | [timeComponents addObject:[NSString stringWithFormat:@"%ld years", (long)years]]; 430 | } 431 | if (months > 0) { 432 | [timeComponents addObject:[NSString stringWithFormat:@"%ld months", (long)months]]; 433 | } 434 | if (days > 0) { 435 | [timeComponents addObject:[NSString stringWithFormat:@"%ld days", (long)days]]; 436 | } 437 | if (hours > 0) { 438 | [timeComponents addObject:[NSString stringWithFormat:@"%ld hours", (long)hours]]; 439 | } 440 | if (minutes > 0) { 441 | [timeComponents addObject:[NSString stringWithFormat:@"%ld minutes", (long)minutes]]; 442 | } 443 | if (seconds > 0) { 444 | [timeComponents addObject:[NSString stringWithFormat:@"%ld seconds", (long)seconds]]; 445 | } 446 | 447 | NSString *message = [NSString stringWithFormat:@"Try again in %@", [timeComponents componentsJoinedByString:@", "]]; 448 | 449 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Locked Out" 450 | message:message 451 | preferredStyle:UIAlertControllerStyleAlert]; 452 | [self.lockWindow.rootViewController presentViewController:alert animated:YES completion:nil]; 453 | } 454 | 455 | 456 | 457 | - (void)initializePasscodeFileIfNeeded { 458 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 459 | NSDictionary *storedData = [defaults objectForKey:@"com.tien0246.ProtectedApp"]; 460 | 461 | if (!storedData || !storedData[@"passcode"]) { 462 | [self savePasscodeToDefaults:@"0000"]; 463 | } 464 | } 465 | 466 | - (NSString *)readPasscodeFromDefaults { 467 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 468 | NSDictionary *storedData = [defaults objectForKey:@"com.tien0246.ProtectedApp"]; 469 | return storedData[@"passcode"]; 470 | } 471 | 472 | - (void)savePasscodeToDefaults:(NSString *)passcode { 473 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 474 | NSMutableDictionary *storedData = [[defaults objectForKey:@"com.tien0246.ProtectedApp"] mutableCopy] ?: [NSMutableDictionary dictionary]; 475 | storedData[@"passcode"] = passcode; 476 | [defaults setObject:storedData forKey:@"com.tien0246.ProtectedApp"]; 477 | [defaults synchronize]; 478 | } 479 | 480 | - (void)loadLockoutState { 481 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 482 | NSDictionary *storedData = [defaults objectForKey:@"com.tien0246.ProtectedApp"]; 483 | 484 | if (storedData) { 485 | self.failedAttempts = [storedData[@"failedAttempts"] integerValue]; 486 | self.lockoutEndTime = storedData[@"lockoutEndTime"]; 487 | } else { 488 | self.failedAttempts = 0; 489 | self.lockoutEndTime = nil; 490 | } 491 | } 492 | 493 | - (void)saveLockoutState { 494 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 495 | NSMutableDictionary *storedData = [[defaults objectForKey:@"com.tien0246.ProtectedApp"] mutableCopy] ?: [NSMutableDictionary dictionary]; 496 | 497 | storedData[@"failedAttempts"] = @(self.failedAttempts); 498 | storedData[@"lockoutEndTime"] = self.lockoutEndTime; 499 | 500 | [defaults setObject:storedData forKey:@"com.tien0246.ProtectedApp"]; 501 | [defaults synchronize]; 502 | } 503 | 504 | @end 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | %hook UIWindow 514 | BOOL isShowLockScreen = NO; 515 | - (void)makeKeyAndVisible { 516 | if (!isShowLockScreen) { 517 | [[ProtectedApp sharedInstance] presentLockScreenIfNeeded]; 518 | isShowLockScreen = YES; 519 | } 520 | return %orig; 521 | } 522 | 523 | %end -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: com.tien0246.protectedapp 2 | Name: ProtectedApp 3 | Version: 1.0.0 4 | Architecture: iphoneos-arm 5 | Description: An awesome ProtectedApp tweak! 6 | Maintainer: tien0246 7 | Author: tien0246 8 | Section: Tweaks 9 | Depends: mobilesubstrate (>= 0.9.5000) 10 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | rm -rf packages 2 | make clean 3 | make package 4 | cp .theos/obj/ProtectedApp.dylib packages 5 | make package THEOS_PACKAGE_SCHEME=rootless 6 | -------------------------------------------------------------------------------- /packages/ProtectedApp.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tien0246/ProtectedApp/c1e18ca75edc8977afc21f7c8d6759c993a71e83/packages/ProtectedApp.dylib -------------------------------------------------------------------------------- /packages/rootful/com.tien0246.protectedapp_1.0.0_iphoneos-arm.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tien0246/ProtectedApp/c1e18ca75edc8977afc21f7c8d6759c993a71e83/packages/rootful/com.tien0246.protectedapp_1.0.0_iphoneos-arm.deb -------------------------------------------------------------------------------- /packages/rootless/com.tien0246.protectedapp_1.0.0_iphoneos-arm64.deb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tien0246/ProtectedApp/c1e18ca75edc8977afc21f7c8d6759c993a71e83/packages/rootless/com.tien0246.protectedapp_1.0.0_iphoneos-arm64.deb --------------------------------------------------------------------------------