Skip to content

Commit

Permalink
Detect and handle app swiching (unstable)
Browse files Browse the repository at this point in the history
  • Loading branch information
khanhduytran0 committed Aug 6, 2024
1 parent a374350 commit 729db4d
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 50 deletions.
6 changes: 5 additions & 1 deletion AppInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@ - (UIImage*)icon {
}

- (UIImage *)generateLiveContainerWrappedIcon {
UIImage *lcIcon = [UIImage imageNamed:@"AppIcon76x76"];
UIImage *icon = self.icon;
if ([NSUserDefaults.standardUserDefaults boolForKey:@"LCDontFrameShortcutIcons"]) {
return icon;
}

UIImage *lcIcon = [UIImage imageNamed:@"AppIcon76x76"];
CGFloat iconXY = (lcIcon.size.width - 40) / 2;
UIGraphicsBeginImageContextWithOptions(lcIcon.size, NO, 0.0);
[lcIcon drawInRect:CGRectMake(0, 0, lcIcon.size.width, lcIcon.size.height)];
Expand Down
14 changes: 1 addition & 13 deletions LCAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
if(![components.host isEqualToString:@"livecontainer-launch"]) return false;

for (NSURLQueryItem* queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"bundle-name"]) {
[NSUserDefaults.standardUserDefaults setObject:queryItem.value forKey:@"selected"];

// Attempt to restart LiveContainer with the selected guest app
[LCUtils launchToGuestApp];
break;
}
}
return true;
return [LCUtils launchToGuestAppWithURL:url];
}

@end
5 changes: 0 additions & 5 deletions LCJITLessSetupViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ - (void)loadView {
self.view.backgroundColor = UIColor.systemBackgroundColor;
self.title = @"LiveContainer JIT-less setup";

if (!LCUtils.certificateData) {
[self showDialogTitle:@"Error" message:@"Failed to find ALTCertificate.p12. Please refresh SideStore and try again." handler:nil];
return;
}

/* TODO: support AltStore
if (!certData) {
certData = [LCUtils keychainItem:@"signingCertificate" ofStore:@"com.rileytestut.AltStore"];
Expand Down
53 changes: 53 additions & 0 deletions LCSharedUtils.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@import UIKit;
#import "LCUtils.h"
#import "UIKitPrivate.h"

extern NSUserDefaults *lcUserDefaults;

@implementation LCSharedUtils
+ (NSString *)certificatePassword {
return [lcUserDefaults objectForKey:@"LCCertificatePassword"];
}

+ (BOOL)launchToGuestApp {
NSString *urlScheme;
NSString *tsPath = [NSString stringWithFormat:@"%@/../_TrollStore", NSBundle.mainBundle.bundlePath];
int tries = 1;
if (!access(tsPath.UTF8String, F_OK)) {
urlScheme = @"apple-magnifier://enable-jit?bundle-id=%@";
} else if (self.certificatePassword) {
tries = 8;
urlScheme = @"livecontainer://livecontainer-relaunch";
} else {
urlScheme = @"sidestore://sidejit-enable?bid=%@";
}
NSURL *launchURL = [NSURL URLWithString:[NSString stringWithFormat:urlScheme, NSBundle.mainBundle.bundleIdentifier]];
if ([UIApplication.sharedApplication canOpenURL:launchURL]) {
//[UIApplication.sharedApplication suspend];
for (int i = 0; i < tries; i++) {
[UIApplication.sharedApplication openURL:launchURL options:@{} completionHandler:^(BOOL b) {
exit(0);
}];
}
return YES;
}
return NO;
}

+ (BOOL)launchToGuestAppWithURL:(NSURL *)url {
NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
if(![components.host isEqualToString:@"livecontainer-launch"]) return NO;

for (NSURLQueryItem* queryItem in components.queryItems) {
if ([queryItem.name isEqualToString:@"bundle-name"]) {
[lcUserDefaults setObject:queryItem.value forKey:@"selected"];

// Attempt to restart LiveContainer with the selected guest app
return [self launchToGuestApp];
break;
}
}
return NO;
}

@end
13 changes: 11 additions & 2 deletions LCUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@

@end

@interface LCSharedUtils : NSObject

+ (NSString *)certificatePassword;
+ (BOOL)launchToGuestApp;
+ (BOOL)launchToGuestAppWithURL:(NSURL *)url;

@end

@interface LCUtils : NSObject

+ (NSData *)certificateData;
+ (NSString *)certificatePassword;
+ (void)setCertificateData:(NSData *)data;
+ (void)setCertificatePassword:(NSString *)password;

+ (BOOL)launchToGuestApp;
+ (BOOL)launchToGuestAppWithURL:(NSURL *)url;

+ (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store;
+ (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL;
+ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler;

+ (BOOL)isAppGroupSideStore;
+ (BOOL)launchToGuestApp;

+ (NSURL *)archiveIPAWithSetupMode:(BOOL)setup error:(NSError **)error;


@end
51 changes: 25 additions & 26 deletions LCUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@implementation LCUtils

#pragma mark Certificate password
#pragma mark Certificate & password

+ (NSString *)appGroupPath {
return [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID].path;
Expand Down Expand Up @@ -41,20 +41,40 @@ + (NSData *)certificateDataFile {
return [NSData dataWithContentsOfURL:url];
}

+ (NSData *)certificateDataProperty {
return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"];
}

+ (NSData *)certificateData {
// Prefer certificate file over keychain data
return self.certificateDataFile ?: [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"];
return self.certificateDataFile ?: self.certificateDataProperty;
}

+ (NSString *)certificatePassword {
if (self.certificateDataFile) {
return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"];;
} else if (self.certificateDataProperty) {
return @"";
} else {
return nil;
}
}

+ (void)setCertificatePassword:(NSString *)certPassword {
[NSUserDefaults.standardUserDefaults setObject:certPassword forKey:@"LCCertificatePassword"];
}

+ (NSString *)certificatePassword {
// Certificate file requires password, whereas data doesn't
return self.certificateDataFile ? [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"] : @"";
#pragma mark LCSharedUtils wrappers
+ (BOOL)launchToGuestApp {
return [NSClassFromString(@"LCSharedUtils") launchToGuestApp];
}

+ (BOOL)launchToGuestAppWithURL:(NSURL *)url {
return [NSClassFromString(@"LCSharedUtils") launchToGuestAppWithURL:url];
}

#pragma mark Code sunning

+ (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL {
int32_t cpusubtype;
sysctlbyname("hw.cpusubtype", &cpusubtype, NULL, NULL, 0);
Expand Down Expand Up @@ -257,25 +277,4 @@ + (NSURL *)archiveIPAWithSetupMode:(BOOL)setup error:(NSError **)error {

return tmpIPAPath;
}

+ (BOOL)launchToGuestApp {
NSString *urlScheme;
NSString *tsPath = [NSString stringWithFormat:@"%@/../_TrollStore", NSBundle.mainBundle.bundlePath];
if (!access(tsPath.UTF8String, F_OK)) {
urlScheme = @"apple-magnifier://enable-jit?bundle-id=%@";
} else if (LCUtils.certificatePassword) {
urlScheme = @"livecontainer://livecontainer-launch?unused=%@";
} else {
urlScheme = @"sidestore://sidejit-enable?bid=%@";
}
NSURL *launchURL = [NSURL URLWithString:[NSString stringWithFormat:urlScheme, NSBundle.mainBundle.bundleIdentifier]];
if ([UIApplication.sharedApplication canOpenURL:launchURL]) {
[UIApplication.sharedApplication openURL:launchURL options:@{} completionHandler:^(BOOL b) {
exit(0);
}];
return true;
}
return false;
}

@end
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ include $(THEOS_MAKE_PATH)/library.mk
# Build the app
APPLICATION_NAME = LiveContainer

$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m fishhook/fishhook.c NSBundle+FixCydiaSubstrate.m
$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m fishhook/fishhook.c LCSharedUtils.m NSBundle+FixCydiaSubstrate.m UIKit+GuestHooks.m
$(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml
$(APPLICATION_NAME)_CFLAGS = -fobjc-arc
$(APPLICATION_NAME)_LDFLAGS = -e_LiveContainerMain -rpath @loader_path/Frameworks
Expand Down
76 changes: 76 additions & 0 deletions UIKit+GuestHooks.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
@import ObjectiveC;
@import UIKit;
#import "LCUtils.h"
#import "UIKitPrivate.h"

static void swizzle(Class class, SEL originalAction, SEL swizzledAction) {
method_exchangeImplementations(class_getInstanceMethod(class, originalAction), class_getInstanceMethod(class, swizzledAction));
}

void UIKitGuestHooksInit() {
swizzle(UIApplication.class, @selector(_applicationOpenURLAction:payload:origin:), @selector(hook__applicationOpenURLAction:payload:origin:));
swizzle(UIScene.class, @selector(scene:didReceiveActions:fromTransitionContext:), @selector(hook_scene:didReceiveActions:fromTransitionContext:));
}

void LCShowSwitchAppConfirmation(NSURL *url) {
NSString *message = [NSString stringWithFormat:@"%@\nAre you sure you want to switch app? Doing so will terminate this app.", url];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[LCSharedUtils launchToGuestAppWithURL:url];
}];
[alert addAction:okAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:cancelAction];
UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIApplication.sharedApplication.windows.lastObject.windowLevel + 1;
window.windowScene = (id)UIApplication.sharedApplication.connectedScenes.anyObject;
[window makeKeyAndVisible];
[window.rootViewController presentViewController:alert animated:YES completion:nil];
objc_setAssociatedObject(alert, @"window", window, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// Handler for AppDelegate
@implementation UIApplication(LiveContainerHook)
- (void)hook__applicationOpenURLAction:(id)action payload:(NSDictionary *)payload origin:(id)origin {
NSString *url = payload[UIApplicationLaunchOptionsURLKey];
if ([url hasPrefix:@"livecontainer://livecontainer-relaunch"]) {
// Ignore
return;
} else if (![url hasPrefix:@"livecontainer://livecontainer-launch?"]) {
// Not what we're looking for, pass it
[self hook__applicationOpenURLAction:action payload:payload origin:origin];
return;
} else if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) {
LCShowSwitchAppConfirmation([NSURL URLWithString:url]);
}
}
@end

// Handler for SceneDelegate
@implementation UIScene(LiveContainerHook)
- (void)hook_scene:(id)scene didReceiveActions:(NSSet *)actions fromTransitionContext:(id)context {
UIOpenURLAction *urlAction;
for (id obj in actions.allObjects) {
if ([obj isKindOfClass:UIOpenURLAction.class]) {
urlAction = obj;
break;
}
}
NSString *url = urlAction.url.absoluteString;
// !urlAction ||
if ([url hasPrefix:@"livecontainer://livecontainer-relaunch"]) {
// Ignore
} else if (![url hasPrefix:@"livecontainer://livecontainer-launch?"]) {
// Not what we're looking for, pass it
[self hook_scene:scene didReceiveActions:actions fromTransitionContext:context];
return;
} else if (![url hasSuffix:NSBundle.mainBundle.bundlePath.lastPathComponent]) {
LCShowSwitchAppConfirmation(urlAction.url);
}

NSMutableSet *newActions = actions.mutableCopy;
[newActions removeObject:urlAction];
[self hook_scene:scene didReceiveActions:newActions fromTransitionContext:context];
}
@end
6 changes: 6 additions & 0 deletions UIKitPrivate.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#import <UIKit/UIKit.h>

void UIKitGuestHooksInit();

@interface NSBundle(private)
- (id)_cfBundle;
@end
Expand Down Expand Up @@ -29,6 +31,10 @@
+ (instancetype)defaultStyle;
@end

@interface UIOpenURLAction : NSObject
- (NSURL *)url;
@end

@interface UITableViewHeaderFooterView(private)
- (void)setText:(NSString *)text;
- (NSString *)text;
Expand Down
8 changes: 6 additions & 2 deletions main.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import <Foundation/Foundation.h>
#import "LCAppDelegate.h"
#import "LCUtils.h"
#import "UIKitPrivate.h"
#import "utils.h"

Expand All @@ -16,7 +17,7 @@

static int (*appMain)(int, char**);
static const char *dyldImageName;
static NSUserDefaults *lcUserDefaults;
NSUserDefaults *lcUserDefaults;

static BOOL checkJITEnabled() {
// check if jailbroken
Expand Down Expand Up @@ -165,7 +166,7 @@ static void overwriteExecPath(NSString *bundlePath) {

static NSString* invokeAppMain(NSString *selectedApp, int argc, char *argv[]) {
NSString *appError = nil;
if (![lcUserDefaults objectForKey:@"LCCertificatePassword"]) {
if (!LCSharedUtils.certificatePassword) {
// First of all, let's check if we have JIT
for (int i = 0; i < 10 && !checkJITEnabled(); i++) {
usleep(1000*100);
Expand Down Expand Up @@ -280,6 +281,9 @@ static void overwriteExecPath(NSString *bundlePath) {
return appError;
}

// Init hooks for guest app
UIKitGuestHooksInit();

// Go!
NSLog(@"[LCBootstrap] jumping to main %p", appMain);
argv[0] = (char *)appExecPath;
Expand Down

0 comments on commit 729db4d

Please sign in to comment.