diff --git a/AltStoreTweak/Makefile b/AltStoreTweak/Makefile index 9054a3d..c64f913 100644 --- a/AltStoreTweak/Makefile +++ b/AltStoreTweak/Makefile @@ -1,4 +1,4 @@ -TARGET := iphone:clang:latest:7.0 +TARGET := iphone:clang:latest:15.0 ARCHS := arm64 include $(THEOS)/makefiles/common.mk diff --git a/AltStoreTweak/Tweak.m b/AltStoreTweak/Tweak.m index ddf6c67..457f3a0 100644 --- a/AltStoreTweak/Tweak.m +++ b/AltStoreTweak/Tweak.m @@ -61,6 +61,7 @@ static void LCAltstoreHookInit(void) { NSUserDefaults* appGroupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appGroupId]; [appGroupUserDefaults setObject:certData forKey:@"LCCertificateData"]; [appGroupUserDefaults setObject:[NSString stringWithUTF8String:certPassword.bytes] forKey:@"LCCertificatePassword"]; + [appGroupUserDefaults setObject:NSDate.now forKey:@"LCCertificateUpdateDate"]; NSLog(@"[LC] Successfully updated JIT-Less certificate!"); synced = YES; } diff --git a/FoundationPrivate.h b/FoundationPrivate.h new file mode 100644 index 0000000..85a4d91 --- /dev/null +++ b/FoundationPrivate.h @@ -0,0 +1,10 @@ +#include + +@interface NSBundle(private) +- (id)_cfBundle; +@end + +@interface NSUserDefaults(private) ++ (void)setStandardUserDefaults:(id)defaults; +- (NSString*)_identifier; +@end diff --git a/LCSharedUtils.h b/LCSharedUtils.h index daeeeb6..444729b 100644 --- a/LCSharedUtils.h +++ b/LCSharedUtils.h @@ -1,7 +1,9 @@ @import Foundation; @interface LCSharedUtils : NSObject ++ (NSString*) teamIdentifier; + (NSString *)appGroupID; ++ (NSURL*) appGroupPath; + (NSString *)certificatePassword; + (BOOL)askForJIT; + (BOOL)launchToGuestApp; diff --git a/LCSharedUtils.m b/LCSharedUtils.m index 669df36..6e0800e 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -3,14 +3,29 @@ extern NSUserDefaults *lcUserDefaults; extern NSString *lcAppUrlScheme; +extern NSBundle *lcMainBundle; @implementation LCSharedUtils ++ (NSString*) teamIdentifier { + static NSString* ans = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + ans = [[lcMainBundle.bundleIdentifier componentsSeparatedByString:@"."] lastObject]; + }); + return ans; +} + + (NSString *)appGroupID { static dispatch_once_t once; - static NSString *appGroupID = @"group.com.SideStore.SideStore"; + static NSString *appGroupID = @"Unknown"; dispatch_once(&once, ^{ - for (NSString *group in NSBundle.mainBundle.infoDictionary[@"ALTAppGroups"]) { + NSArray* possibleAppGroups = @[ + [@"group.com.SideStore.SideStore." stringByAppendingString:[self teamIdentifier]], + [@"group.com.rileytestut.AltStore." stringByAppendingString:[self teamIdentifier]] + ]; + + for (NSString *group in possibleAppGroups) { NSURL *path = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:group]; NSURL *bundlePath = [path URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer/App.app"]; if ([NSFileManager.defaultManager fileExistsAtPath:bundlePath.path]) { @@ -23,12 +38,22 @@ + (NSString *)appGroupID { return appGroupID; } ++ (NSURL*) appGroupPath { + static NSURL *appGroupPath = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; + }); + return appGroupPath; +} + + (NSString *)certificatePassword { + // password of cert retrieved from the store tweak is always @"". We just keep this function so we can check if certificate presents without changing codes. NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; if(ans) { - return ans; + return @""; } else { - return [lcUserDefaults objectForKey:@"LCCertificatePassword"]; + return nil; } } @@ -45,10 +70,12 @@ + (BOOL)launchToGuestApp { urlScheme = @"sidestore://sidejit-enable?bid=%@"; } NSURL *launchURL = [NSURL URLWithString:[NSString stringWithFormat:urlScheme, NSBundle.mainBundle.bundleIdentifier]]; - if ([UIApplication.sharedApplication canOpenURL:launchURL]) { + + UIApplication *application = [NSClassFromString(@"UIApplication") sharedApplication]; + if ([application canOpenURL:launchURL]) { //[UIApplication.sharedApplication suspend]; for (int i = 0; i < tries; i++) { - [UIApplication.sharedApplication openURL:launchURL options:@{} completionHandler:^(BOOL b) { + [application openURL:launchURL options:@{} completionHandler:^(BOOL b) { exit(0); }]; } @@ -63,8 +90,9 @@ + (BOOL)askForJIT { if (!access(tsPath.UTF8String, F_OK)) { urlScheme = @"apple-magnifier://enable-jit?bundle-id=%@"; NSURL *launchURL = [NSURL URLWithString:[NSString stringWithFormat:urlScheme, NSBundle.mainBundle.bundleIdentifier]]; - if ([UIApplication.sharedApplication canOpenURL:launchURL]) { - [UIApplication.sharedApplication openURL:launchURL options:@{} completionHandler:nil]; + UIApplication *application = [NSClassFromString(@"UIApplication") sharedApplication]; + if ([application canOpenURL:launchURL]) { + [application openURL:launchURL options:@{} completionHandler:nil]; [LCSharedUtils launchToGuestApp]; return YES; } @@ -131,8 +159,7 @@ + (NSURL*)appLockPath { static NSURL *infoPath; dispatch_once(&once, ^{ - NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; - infoPath = [appGroupPath URLByAppendingPathComponent:@"LiveContainer/appLock.plist"]; + infoPath = [[LCSharedUtils appGroupPath] URLByAppendingPathComponent:@"LiveContainer/appLock.plist"]; }); return infoPath; } @@ -142,8 +169,7 @@ + (NSURL*)containerLockPath { static NSURL *infoPath; dispatch_once(&once, ^{ - NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; - infoPath = [appGroupPath URLByAppendingPathComponent:@"LiveContainer/containerLock.plist"]; + infoPath = [[LCSharedUtils appGroupPath] URLByAppendingPathComponent:@"LiveContainer/containerLock.plist"]; }); return infoPath; } @@ -250,8 +276,7 @@ + (void)moveSharedAppFolderBack { .lastObject; NSURL *docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] .lastObject; - NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; - NSURL *appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"]; + NSURL *appGroupFolder = [[LCSharedUtils appGroupPath] URLByAppendingPathComponent:@"LiveContainer"]; NSError *error; NSString *sharedAppDataFolderPath = [libraryPathUrl.path stringByAppendingPathComponent:@"SharedDocuments"]; @@ -289,8 +314,7 @@ + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId { NSBundle *appBundle = [[NSBundle alloc] initWithPath:bundlePath]; // not found locally, let's look for the app in shared folder if (!appBundle) { - NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; - appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"]; + appGroupFolder = [[LCSharedUtils appGroupPath] URLByAppendingPathComponent:@"LiveContainer"]; bundlePath = [NSString stringWithFormat:@"%@/Applications/%@", appGroupFolder.path, bundleId]; appBundle = [[NSBundle alloc] initWithPath:bundlePath]; @@ -323,10 +347,9 @@ + (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)data + (NSString*)findDefaultContainerWithBundleId:(NSString*)bundleId { // find app's default container - NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; - NSURL* appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"]; + NSURL* appGroupFolder = [[LCSharedUtils appGroupPath] URLByAppendingPathComponent:@"LiveContainer"]; - NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", appGroupFolder.path, bundleId]; + NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/LCAppInfo.plist", appGroupFolder.path, bundleId]; NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; return infoDict[@"LCDataUUID"]; } diff --git a/LiveContainerSwiftUI/AppDelegate.swift b/LiveContainerSwiftUI/AppDelegate.swift index 699395e..00d9f4b 100644 --- a/LiveContainerSwiftUI/AppDelegate.swift +++ b/LiveContainerSwiftUI/AppDelegate.swift @@ -52,6 +52,7 @@ import SwiftUI let contentView = LCTabView() window.rootViewController = UIHostingController(rootView: contentView) window.makeKeyAndVisible() + application.shortcutItems = nil return true } diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index 26336e5..6b5928b 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -9,8 +9,9 @@ typedef NS_ENUM(NSInteger, LCOrientationLock){ }; @interface LCAppInfo : NSObject { - NSMutableDictionary* _info; - NSString* _bundlePath; + NSMutableDictionary* _info; + NSMutableDictionary* _infoPlist; + NSString* _bundlePath; } @property NSString* relativeBundlePath; @property bool isShared; @@ -18,6 +19,8 @@ typedef NS_ENUM(NSInteger, LCOrientationLock){ @property bool isLocked; @property bool isHidden; @property bool doSymlinkInbox; +@property bool ignoreDlopenError; +@property bool fixBlackScreen; @property bool bypassAssertBarrierOnQueue; @property UIColor* cachedColor; @property Signer signer; diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 0ae52ab..4d4ac39 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -11,7 +11,46 @@ - (instancetype)initWithBundlePath:(NSString*)bundlePath { self.isShared = false; if(self) { _bundlePath = bundlePath; - _info = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]]; + _infoPlist = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]]; + _info = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/LCAppInfo.plist", bundlePath]]; + if(!_info) { + _info = [[NSMutableDictionary alloc] init]; + } + if(!_infoPlist) { + _infoPlist = [[NSMutableDictionary alloc] init]; + } + + // migrate old appInfo + if(_infoPlist[@"LCPatchRevision"] && [_info count] == 0) { + NSArray* lcAppInfoKeys = @[ + @"LCPatchRevision", + @"LCOrignalBundleIdentifier", + @"LCDataUUID", + @"LCTweakFolder", + @"LCJITLessSignID", + @"LCSelectedLanguage", + @"LCExpirationDate", + @"LCTeamId", + @"isJITNeeded", + @"isLocked", + @"isHidden", + @"doUseLCBundleId", + @"doSymlinkInbox", + @"bypassAssertBarrierOnQueue", + @"signer", + @"LCOrientationLock", + @"cachedColor", + @"LCContainers", + @"ignoreDlopenError" + ]; + for(NSString* key in lcAppInfoKeys) { + _info[key] = _infoPlist[key]; + [_infoPlist removeObjectForKey:key]; + } + [_infoPlist writeToFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath] atomically:YES]; + [self save]; + } + _autoSaveDisabled = false; } return self; @@ -25,8 +64,8 @@ - (NSMutableArray*)urlSchemes { // find all url schemes NSMutableArray* urlSchemes = [[NSMutableArray alloc] init]; int nowSchemeCount = 0; - if (_info[@"CFBundleURLTypes"]) { - NSMutableArray* urlTypes = _info[@"CFBundleURLTypes"]; + if (_infoPlist[@"CFBundleURLTypes"]) { + NSMutableArray* urlTypes = _infoPlist[@"CFBundleURLTypes"]; for(int i = 0; i < [urlTypes count]; ++i) { NSMutableDictionary* nowUrlType = [urlTypes objectAtIndex:i]; @@ -45,30 +84,40 @@ - (NSMutableArray*)urlSchemes { } - (NSString*)displayName { - if (_info[@"CFBundleDisplayName"]) { - return _info[@"CFBundleDisplayName"]; - } else if (_info[@"CFBundleName"]) { - return _info[@"CFBundleName"]; - } else if (_info[@"CFBundleExecutable"]) { - return _info[@"CFBundleExecutable"]; + if (_infoPlist[@"CFBundleDisplayName"]) { + return _infoPlist[@"CFBundleDisplayName"]; + } else if (_infoPlist[@"CFBundleName"]) { + return _infoPlist[@"CFBundleName"]; + } else if (_infoPlist[@"CFBundleExecutable"]) { + return _infoPlist[@"CFBundleExecutable"]; } else { - return nil; + return @"App Corrupted, Please Reinstall This App"; } } - (NSString*)version { - NSString* version = _info[@"CFBundleShortVersionString"]; + NSString* version = _infoPlist[@"CFBundleShortVersionString"]; if (!version) { - version = _info[@"CFBundleVersion"]; + version = _infoPlist[@"CFBundleVersion"]; + } + if(version) { + return version; + } else { + return @"Unknown"; } - return version; } - (NSString*)bundleIdentifier { + NSString* ans = nil; if([self doUseLCBundleId]) { - return _info[@"LCOrignalBundleIdentifier"]; + ans = _info[@"LCOrignalBundleIdentifier"]; } else { - return _info[@"CFBundleIdentifier"]; + ans = _infoPlist[@"CFBundleIdentifier"]; + } + if(ans) { + return ans; + } else { + return @"Unknown"; } } @@ -113,13 +162,13 @@ - (NSMutableDictionary*)info { } - (UIImage*)icon { - UIImage* icon = [UIImage imageNamed:[_info valueForKeyPath:@"CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles"][0] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; + UIImage* icon = [UIImage imageNamed:[_infoPlist valueForKeyPath:@"CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles"][0] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; if(!icon) { - icon = [UIImage imageNamed:[_info valueForKeyPath:@"CFBundleIconFiles"][0] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; + icon = [UIImage imageNamed:[_infoPlist valueForKeyPath:@"CFBundleIconFiles"][0] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; } if(!icon) { - icon = [UIImage imageNamed:[_info valueForKeyPath:@"CFBundleIcons~ipad"][@"CFBundlePrimaryIcon"][@"CFBundleIconName"] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; + icon = [UIImage imageNamed:[_infoPlist valueForKeyPath:@"CFBundleIcons~ipad"][@"CFBundlePrimaryIcon"][@"CFBundleIconName"] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; } if(!icon) { @@ -188,7 +237,7 @@ - (NSDictionary *)generateWebClipConfigWithContainerId:(NSString*)containerId { - (void)save { if(!_autoSaveDisabled) { - [_info writeToFile:[NSString stringWithFormat:@"%@/Info.plist", _bundlePath] atomically:YES]; + [_info writeToFile:[NSString stringWithFormat:@"%@/LCAppInfo.plist", _bundlePath] atomically:YES]; } } @@ -212,6 +261,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr NSString *appPath = self.bundlePath; NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", appPath]; NSMutableDictionary *info = _info; + NSMutableDictionary *infoPlist = _infoPlist; if (!info) { completetionHandler(NO, @"Info.plist not found"); return; @@ -220,7 +270,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr // Update patch int currentPatchRev = 5; if ([info[@"LCPatchRevision"] intValue] < currentPatchRev) { - NSString *execPath = [NSString stringWithFormat:@"%@/%@", appPath, info[@"CFBundleExecutable"]]; + NSString *execPath = [NSString stringWithFormat:@"%@/%@", appPath, _infoPlist[@"CFBundleExecutable"]]; NSString *error = LCParseMachO(execPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { LCPatchExecSlice(path, header); }); @@ -229,7 +279,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr return; } info[@"LCPatchRevision"] = @(currentPatchRev); - [info writeToFile:infoPath atomically:YES]; + [self save]; } if (!LCUtils.certificatePassword) { @@ -240,7 +290,8 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr int signRevision = 1; NSDate* expirationDate = info[@"LCExpirationDate"]; - if(expirationDate && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"] && !forceSign) { + NSString* teamId = info[@"LCTeamId"]; + if(expirationDate && [teamId isEqualToString:[LCUtils teamIdentifier]] && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"] && !forceSign) { if([expirationDate laterDate:[NSDate now]] == expirationDate) { // not expired yet, don't sign again completetionHandler(YES, nil); @@ -269,18 +320,18 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr // Don't let main executable get entitlements [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil]; - info[@"LCBundleExecutable"] = info[@"CFBundleExecutable"]; - info[@"LCBundleIdentifier"] = info[@"CFBundleIdentifier"]; - info[@"CFBundleExecutable"] = tmpExecPath.lastPathComponent; - info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; - [info writeToFile:infoPath atomically:YES]; + infoPlist[@"LCBundleExecutable"] = infoPlist[@"CFBundleExecutable"]; + infoPlist[@"LCBundleIdentifier"] = infoPlist[@"CFBundleIdentifier"]; + infoPlist[@"CFBundleExecutable"] = tmpExecPath.lastPathComponent; + infoPlist[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; + [infoPlist writeToFile:infoPath atomically:YES]; } - info[@"CFBundleExecutable"] = info[@"LCBundleExecutable"]; - info[@"CFBundleIdentifier"] = info[@"LCBundleIdentifier"]; - [info removeObjectForKey:@"LCBundleExecutable"]; - [info removeObjectForKey:@"LCBundleIdentifier"]; + infoPlist[@"CFBundleExecutable"] = infoPlist[@"LCBundleExecutable"]; + infoPlist[@"CFBundleIdentifier"] = infoPlist[@"LCBundleIdentifier"]; + [infoPlist removeObjectForKey:@"LCBundleExecutable"]; + [infoPlist removeObjectForKey:@"LCBundleIdentifier"]; - void (^signCompletionHandler)(BOOL success, NSDate* expirationDate, NSError *error) = ^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { + void (^signCompletionHandler)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error) = ^(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { info[@"LCJITLessSignID"] = @(signID); @@ -293,9 +344,12 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSStr if(success && expirationDate) { info[@"LCExpirationDate"] = expirationDate; } + if(success && teamId) { + info[@"LCTeamId"] = teamId; + } // Save sign ID and restore bundle ID [self save]; - + [infoPlist writeToFile:infoPath atomically:YES]; completetionHandler(success, error.localizedDescription); }); @@ -380,6 +434,31 @@ - (void)setDoSymlinkInbox:(bool)doSymlinkInbox { } +- (bool)ignoreDlopenError { + if(_info[@"ignoreDlopenError"] != nil) { + return [_info[@"ignoreDlopenError"] boolValue]; + } else { + return NO; + } +} +- (void)setIgnoreDlopenError:(bool)ignoreDlopenError { + _info[@"ignoreDlopenError"] = [NSNumber numberWithBool:ignoreDlopenError]; + [self save]; +} + +- (bool)fixBlackScreen { + if(_info[@"fixBlackScreen"] != nil) { + return [_info[@"fixBlackScreen"] boolValue]; + } else { + return NO; + } +} +- (void)setFixBlackScreen:(bool)fixBlackScreen { + _info[@"fixBlackScreen"] = [NSNumber numberWithBool:fixBlackScreen]; + [self save]; +} + + - (bool)doUseLCBundleId { if(_info[@"doUseLCBundleId"] != nil) { return [_info[@"doUseLCBundleId"] boolValue]; @@ -389,13 +468,15 @@ - (bool)doUseLCBundleId { } - (void)setDoUseLCBundleId:(bool)doUseLCBundleId { _info[@"doUseLCBundleId"] = [NSNumber numberWithBool:doUseLCBundleId]; + NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", self.bundlePath]; if(doUseLCBundleId) { - _info[@"LCOrignalBundleIdentifier"] = _info[@"CFBundleIdentifier"]; - _info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; + _info[@"LCOrignalBundleIdentifier"] = _infoPlist[@"CFBundleIdentifier"]; + _infoPlist[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; } else if (_info[@"LCOrignalBundleIdentifier"]) { - _info[@"CFBundleIdentifier"] = _info[@"LCOrignalBundleIdentifier"]; + _infoPlist[@"CFBundleIdentifier"] = _info[@"LCOrignalBundleIdentifier"]; [_info removeObjectForKey:@"LCOrignalBundleIdentifier"]; } + [_infoPlist writeToFile:infoPath atomically:YES]; [self save]; } diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 855df98..715eb3e 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -44,6 +44,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { @State private var navigateTo : AnyView? @State private var isNavigationActive = false + @State private var helpPresent = false + @EnvironmentObject private var sharedModel : SharedModel init(appDataFolderNames: Binding<[String]>, tweakFolderNames: Binding<[String]>) { @@ -169,6 +171,12 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } } } + ToolbarItem(placement: .topBarLeading) { + Button("Help", systemImage: "questionmark") { + helpPresent = true + } + } + ToolbarItem(placement: .topBarTrailing) { Button("lc.appList.openLink".loc, systemImage: "link", action: { Task { await onOpenWebViewTapped() } @@ -228,6 +236,9 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { .fullScreenCover(isPresented: $safariViewOpened) { SafariView(url: $safariViewURL) } + .sheet(isPresented: $helpPresent) { + LCHelpView(isPresent: $helpPresent) + } } @@ -479,6 +490,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.selectedLanguage = appToReplace.appInfo.selectedLanguage finalNewApp.dataUUID = appToReplace.appInfo.dataUUID finalNewApp.orientationLock = appToReplace.appInfo.orientationLock + finalNewApp.ignoreDlopenError = appToReplace.appInfo.ignoreDlopenError finalNewApp.autoSaveDisabled = false finalNewApp.save() } diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index b4a2719..83c31d4 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -26,6 +26,8 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiUseLCBundleId : Bool @Published var uiBypassAssertBarrierOnQueue : Bool @Published var uiSigner : Signer + @Published var uiIgnoreDlopenError : Bool + @Published var uiFixBlackScreen : Bool @Published var uiOrientationLock : LCOrientationLock @Published var uiSelectedLanguage : String @Published var supportedLanaguages : [String]? @@ -55,6 +57,8 @@ class LCAppModel: ObservableObject, Hashable { self.uiSigner = appInfo.signer self.uiOrientationLock = appInfo.orientationLock self.uiUseLCBundleId = appInfo.doUseLCBundleId + self.uiIgnoreDlopenError = appInfo.ignoreDlopenError + self.uiFixBlackScreen = appInfo.fixBlackScreen for container in uiContainers { if container.folderName == uiDefaultDataFolder { @@ -79,7 +83,7 @@ class LCAppModel: ObservableObject, Hashable { if uiContainers.isEmpty { let newName = NSUUID().uuidString - let newContainer = LCContainer(folderName: newName, name: newName, isShared: uiIsShared) + let newContainer = LCContainer(folderName: newName, name: newName, isShared: uiIsShared, isolateAppGroup: false) uiContainers.append(newContainer) if uiSelectedContainer == nil { uiSelectedContainer = newContainer; diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 6e09644..0200d78 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -201,7 +201,7 @@ struct LCAppSettingsView : View{ } } else { - Text("lc.appSettings.languageLoading".loc) + Text("lc.common.loading".loc) .onAppear() { Task{ loadSupportedLanguages() } } @@ -250,6 +250,28 @@ struct LCAppSettingsView : View{ }) } } + + Section { + Toggle(isOn: $model.uiIgnoreDlopenError) { + Text("lc.appSettings.ignoreDlopenError".loc) + } + .onChange(of: model.uiIgnoreDlopenError, perform: { newValue in + Task { await setIgnoreDlopenError(newValue) } + }) + } footer: { + Text("lc.appSettings.ignoreDlopenErrorDesc".loc) + } + + Section { + Toggle(isOn: $model.uiFixBlackScreen) { + Text("lc.appSettings.fixBlackScreen".loc) + } + .onChange(of: model.uiFixBlackScreen, perform: { newValue in + Task { await setFixBlackScreen(newValue) } + }) + } footer: { + Text("lc.appSettings.fixBlackScreenDesc".loc) + } Section { @@ -358,7 +380,7 @@ struct LCAppSettingsView : View{ } self.appDataFolders.append(newName) - let newContainer = LCContainer(folderName: newName, name: displayName, isShared: model.uiIsShared) + let newContainer = LCContainer(folderName: newName, name: displayName, isShared: model.uiIsShared, isolateAppGroup: false) // assign keychain group var keychainGroupSet : Set = Set(minimumCapacity: 3) for i in 0...2 { @@ -492,6 +514,16 @@ struct LCAppSettingsView : View{ model.uiUseLCBundleId = doUseLCBundleId } + func setIgnoreDlopenError(_ ignoreDlopenError : Bool) async { + appInfo.ignoreDlopenError = ignoreDlopenError + model.uiIgnoreDlopenError = ignoreDlopenError + } + + func setFixBlackScreen(_ fixBlackScreen : Bool) async { + appInfo.fixBlackScreen = fixBlackScreen + model.uiFixBlackScreen = fixBlackScreen + } + func setOrientationLock(_ lock : LCOrientationLock) async { appInfo.orientationLock = lock model.uiOrientationLock = lock @@ -604,7 +636,7 @@ extension LCAppSettingsView : LCSelectContainerViewDelegate { } for folderName in containers { - let newContainer = LCContainer(folderName: folderName, name: folderName, isShared: false) + let newContainer = LCContainer(folderName: folderName, name: folderName, isShared: false, isolateAppGroup: false) newContainer.loadName() if newContainer.keychainGroupId == -1 { // assign keychain group for old containers diff --git a/LiveContainerSwiftUI/LCContainer.swift b/LiveContainerSwiftUI/LCContainer.swift index 01a8e20..8b57d39 100644 --- a/LiveContainerSwiftUI/LCContainer.swift +++ b/LiveContainerSwiftUI/LCContainer.swift @@ -12,6 +12,7 @@ class LCContainer : ObservableObject, Hashable { @Published var folderName : String @Published var name : String @Published var isShared : Bool + @Published var isolateAppGroup : Bool private var infoDict : [String:Any]? public var containerURL : URL { if isShared { @@ -47,20 +48,26 @@ class LCContainer : ObservableObject, Hashable { } } - init(folderName: String, name: String, isShared : Bool) { + init(folderName: String, name: String, isShared : Bool, isolateAppGroup: Bool) { self.folderName = folderName self.name = name self.isShared = isShared + self.isolateAppGroup = isolateAppGroup } convenience init(infoDict : [String : Any], isShared : Bool) { - self.init(folderName: infoDict["folderName"] as? String ?? "ERROR", name: infoDict["name"] as? String ?? "ERROR", isShared: isShared) + self.init(folderName: infoDict["folderName"] as? String ?? "ERROR", + name: infoDict["name"] as? String ?? "ERROR", + isShared: isShared, + isolateAppGroup: infoDict["isolateAppGroup"] as? Bool ?? false + ) } func toDict() -> [String : Any] { return [ "folderName" : folderName, "name" : name, + "isolateAppGroup" : isolateAppGroup ] } @@ -68,7 +75,8 @@ class LCContainer : ObservableObject, Hashable { infoDict = [ "appIdentifier" : appIdentifier, "name" : name, - "keychainGroupId" : keychainGroupId + "keychainGroupId" : keychainGroupId, + "isolateAppGroup" : isolateAppGroup, ] do { let fm = FileManager.default @@ -94,6 +102,7 @@ class LCContainer : ObservableObject, Hashable { return } name = infoDict["name"] as? String ?? "ERROR" + isolateAppGroup = infoDict["isolateAppGroup"] as? Bool ?? false } static func == (lhs: LCContainer, rhs: LCContainer) -> Bool { @@ -114,6 +123,7 @@ extension LCAppInfo { containerInfo = [[ "folderName": oldDataUUID, "name": oldDataUUID, + "isolateAppGroup": false ]] upgrade = true } diff --git a/LiveContainerSwiftUI/LCContainerView.swift b/LiveContainerSwiftUI/LCContainerView.swift index e9f4ca2..b0f2b44 100644 --- a/LiveContainerSwiftUI/LCContainerView.swift +++ b/LiveContainerSwiftUI/LCContainerView.swift @@ -61,6 +61,13 @@ struct LCContainerView : View { Text(container.folderName) .foregroundStyle(.gray) } + Toggle(isOn: $container.isolateAppGroup) { + Text("lc.container.isolateAppGroup".loc) + } + .onChange(of: container.isolateAppGroup) { newValue in + saveContainer() + } + if let settingsBundle { NavigationLink { AppPreferenceView(bundleId: delegate.getBundleId(), settingsBundle: settingsBundle, userDefaultsURL: delegate.getUserDefaultsURL(container: container)) diff --git a/LiveContainerSwiftUI/LCHelpView.swift b/LiveContainerSwiftUI/LCHelpView.swift new file mode 100644 index 0000000..63f80fb --- /dev/null +++ b/LiveContainerSwiftUI/LCHelpView.swift @@ -0,0 +1,34 @@ +// +// LCHelpView.swift +// LiveContainerSwiftUI +// +// Created by s s on 2025/1/4. +// + +import SwiftUI + +struct LCHelpView : View { + @Binding var isPresent : Bool + + var body: some View { + NavigationView { + ScrollView { + VStack(alignment: .leading) { + Text("lc.helpView.text1") + Text("") + Text("lc.helpView.text2") + } + .padding() + } + .navigationTitle("lc.helpView.title") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem { + Button("lc.common.done") { + isPresent = false + } + } + } + } + } +} diff --git a/LiveContainerSwiftUI/LCJITLessDiagnoseView.swift b/LiveContainerSwiftUI/LCJITLessDiagnoseView.swift new file mode 100644 index 0000000..654d5b1 --- /dev/null +++ b/LiveContainerSwiftUI/LCJITLessDiagnoseView.swift @@ -0,0 +1,203 @@ +// +// LCJITLessDiagnose.swift +// LiveContainerSwiftUI +// +// Created by s s on 2024/12/19. +// +import SwiftUI + +struct LCJITLessDiagnoseView : View { + @State var loaded = false + @State var appGroupId = "Unknown" + @State var store : Store = .SideStore + @State var isPatchDetected = false + @State var certificateDataFound = false + @State var certificatePasswordFound = false + @State var appGroupAccessible = false + @State var certLastUpdateDateStr : String? = nil + + @State var isJITLessTestInProgress = false + + @State var errorShow = false + @State var errorInfo = "" + @State var successShow = false + @State var successInfo = "" + + let storeName = LCUtils.getStoreName() + + var body: some View { + if loaded { + Form { + Section { + HStack { + Text("lc.jitlessDiag.bundleId".loc) + Spacer() + Text(Bundle.main.bundleIdentifier ?? "lc.common.unknown".loc) + .foregroundStyle(.gray) + } + HStack { + Text("lc.jitlessDiag.appGroupId".loc) + Spacer() + Text(appGroupId) + .foregroundStyle(appGroupId == "Unknown" ? .red : .green) + } + HStack { + Text("lc.jitlessDiag.appGroupAccessible".loc) + Spacer() + Text(appGroupAccessible ? "lc.common.yes".loc : "lc.common.no".loc) + .foregroundStyle(appGroupAccessible ? .green : .red) + } + HStack { + Text("lc.jitlessDiag.store".loc) + Spacer() + if store == .AltStore { + Text("AltStore") + .foregroundStyle(.gray) + } else { + Text("SideStore") + .foregroundStyle(.gray) + } + } + HStack { + Text("lc.jitlessDiag.patchDetected".loc) + Spacer() + Text(isPatchDetected ? "lc.common.yes".loc : "lc.common.no".loc) + .foregroundStyle(isPatchDetected ? .green : .red) + } + + HStack { + Text("lc.jitlessDiag.certDataFound".loc) + Spacer() + Text(certificateDataFound ? "lc.common.yes".loc : "lc.common.no".loc) + .foregroundStyle(certificateDataFound ? .green : .red) + + } + HStack { + Text("lc.jitlessDiag.certPassFound".loc) + Spacer() + Text(certificatePasswordFound ? "lc.common.yes".loc : "lc.common.no".loc) + .foregroundStyle(certificatePasswordFound ? .green : .red) + } + + HStack { + Text("lc.jitlessDiag.certLastUpdate".loc) + Spacer() + if let certLastUpdateDateStr { + Text(certLastUpdateDateStr) + .foregroundStyle(.green) + } else { + Text("lc.common.unknown".loc) + .foregroundStyle(.red) + } + + } + + Button { + testJITLessMode() + } label: { + Text("lc.settings.testJitLess".loc) + } + .disabled(isJITLessTestInProgress) + } + + Section { + Button { + getHelp() + } label: { + Text("lc.jitlessDiag.getHelp".loc) + } + } + + } + .navigationTitle("lc.settings.jitlessDiagnose".loc) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + onAppear() + } label: { + Image(systemName: "arrow.clockwise") + } + + } + } + .alert("lc.common.error".loc, isPresented: $errorShow){ + } message: { + Text(errorInfo) + } + .alert("lc.common.success".loc, isPresented: $successShow){ + } message: { + Text(successInfo) + } + + } else { + Text("lc.common.loading".loc) + .onAppear() { + onAppear() + } + } + + } + + func onAppear() { + appGroupId = LCUtils.appGroupID() ?? "lc.common.unknown".loc + store = LCUtils.store() + isPatchDetected = checkIsPatched() + appGroupAccessible = LCUtils.appGroupPath() != nil + certificateDataFound = LCUtils.certificateData() != nil + certificatePasswordFound = LCUtils.certificatePassword() != nil + if let lastUpdateDate = LCUtils.appGroupUserDefault.object(forKey: "LCCertificateUpdateDate") as? Date { + let formatter1 = DateFormatter() + formatter1.dateStyle = .short + formatter1.timeStyle = .medium + certLastUpdateDateStr = formatter1.string(from: lastUpdateDate) + } + + + loaded = true + } + + func checkIsPatched() -> Bool { + let fm = FileManager.default + guard let appGroupURL = LCUtils.appGroupPath() else { + return false + } + let patchPath : URL + if LCUtils.store() == .AltStore { + patchPath = appGroupURL.appendingPathComponent("Apps/com.rileytestut.AltStore/App.app/Frameworks/AltStoreTweak.dylib") + } else { + patchPath = appGroupURL.appendingPathComponent("Apps/com.SideStore.SideStore/App.app/Frameworks/AltStoreTweak.dylib") + } + return fm.fileExists(atPath: patchPath.path) + } + + func testJITLessMode() { + if !LCUtils.isAppGroupAltStoreLike() { + errorInfo = "lc.settings.unsupportedInstallMethod".loc + errorShow = true + return; + } + + if !isPatchDetected { + errorInfo = "lc.settings.error.storeNotPatched %@".localizeWithFormat(storeName) + errorShow = true + return; + } + isJITLessTestInProgress = true + LCUtils.validateJITLessSetup(with: Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))!) { success, error in + if success { + successInfo = "lc.jitlessSetup.success".loc + successShow = true + } else { + errorInfo = "lc.jitlessSetup.error.testLibLoadFailed %@ %@ %@".localizeWithFormat(storeName, storeName, storeName) + "\n" + (error?.localizedDescription ?? "") + errorShow = true + } + isJITLessTestInProgress = false + } + + } + + func getHelp() { + UIApplication.shared.open(URL(string: "https://github.com/khanhduytran0/LiveContainer/issues/265#issuecomment-2558409380")!) + } +} diff --git a/LiveContainerSwiftUI/LCSelectContainerView.swift b/LiveContainerSwiftUI/LCSelectContainerView.swift index 3829e36..14737c0 100644 --- a/LiveContainerSwiftUI/LCSelectContainerView.swift +++ b/LiveContainerSwiftUI/LCSelectContainerView.swift @@ -50,7 +50,7 @@ struct LCSelectContainerView : View{ isPresent = false delegate.addContainers(containers: multiSelection) } label: { - Text("lc.common.ok".loc) + Text("lc.common.done".loc) } } } @@ -106,7 +106,7 @@ struct LCSelectContainerView : View{ } unusedContainers = unusedFolders.map { folder in - let ans = LCContainer(folderName: folder, name: folder, isShared: false) + let ans = LCContainer(folderName: folder, name: folder, isShared: false, isolateAppGroup: false) ans.loadName() return ans; } diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index 17adb08..f38b35e 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -31,11 +31,10 @@ struct LCSettingsView: View { @State var isJitLessEnabled = false @State var defaultSigner = Signer.ZSign - @State var isJITLessTestInProgress = false @State var isSignOnlyOnExpiration = true @State var frameShortIcon = false @State var silentSwitchApp = false - @State var injectToLCItelf = false + @State var silentOpenWebPage = false @State var strictHiding = false @AppStorage("dynamicColors") var dynamicColors = true @@ -44,6 +43,9 @@ struct LCSettingsView: View { @State var isSideStore : Bool = true + @State var injectToLCItelf = false + @State var ignoreJITOnLaunch = false + @EnvironmentObject private var sharedModel : SharedModel let storeName = LCUtils.getStoreName() @@ -59,7 +61,9 @@ struct LCSettingsView: View { _isSignOnlyOnExpiration = State(initialValue: LCUtils.appGroupUserDefault.bool(forKey: "LCSignOnlyOnExpiration")) _frameShortIcon = State(initialValue: UserDefaults.standard.bool(forKey: "LCFrameShortcutIcons")) _silentSwitchApp = State(initialValue: UserDefaults.standard.bool(forKey: "LCSwitchAppWithoutAsking")) + _silentOpenWebPage = State(initialValue: UserDefaults.standard.bool(forKey: "LCOpenWebPageWithoutAsking")) _injectToLCItelf = State(initialValue: UserDefaults.standard.bool(forKey: "LCLoadTweaksToSelf")) + _ignoreJITOnLaunch = State(initialValue: UserDefaults.standard.bool(forKey: "LCIgnoreJITOnLaunch")) _isSideStore = State(initialValue: LCUtils.store() == .SideStore) @@ -96,25 +100,17 @@ struct LCSettingsView: View { } } + NavigationLink { + LCJITLessDiagnoseView() + } label: { + Text("lc.settings.jitlessDiagnose".loc) + } + if isAltStorePatched { - Button { - testJITLessMode() - } label: { - Text("lc.settings.testJitLess".loc) - } - .disabled(isJITLessTestInProgress) Toggle(isOn: $isSignOnlyOnExpiration) { Text("lc.settings.signOnlyOnExpiration".loc) } } - if sharedModel.developerMode { - Button { - export() - } label: { - Text("export cert") - } - } - Picker(selection: $defaultSigner) { Text("AltSign").tag(Signer.AltSign) @@ -144,6 +140,14 @@ struct LCSettingsView: View { } .disabled(sharedModel.multiLCStatus == 2) + + if(sharedModel.multiLCStatus == 2) { + NavigationLink { + LCJITLessDiagnoseView() + } label: { + Text("lc.settings.jitlessDiagnose".loc) + } + } } header: { Text("lc.settings.multiLC".loc) } footer: { @@ -195,16 +199,15 @@ struct LCSettingsView: View { } footer: { Text("lc.settings.silentSwitchAppDesc".loc) } - if sharedModel.developerMode { - Section { - Toggle(isOn: $injectToLCItelf) { - Text("lc.settings.injectLCItself".loc) - } - } footer: { - Text("lc.settings.injectLCItselfDesc".loc) + + Section { + Toggle(isOn: $silentOpenWebPage) { + Text("lc.settings.silentOpenWebPage".loc) } + } footer: { + Text("lc.settings.silentOpenWebPageDesc".loc) } - + if sharedModel.isHiddenAppUnlocked { Section { Toggle(isOn: $strictHiding) { @@ -247,6 +250,26 @@ struct LCSettingsView: View { } } + if sharedModel.developerMode { + Section { + Toggle(isOn: $injectToLCItelf) { + Text("lc.settings.injectLCItself".loc) + } + Toggle(isOn: $ignoreJITOnLaunch) { + Text("Ignore JIT on Launching App") + } + Button { + export() + } label: { + Text("export cert") + } + } header: { + Text("Developer Settings") + } footer: { + Text("lc.settings.injectLCItselfDesc".loc) + } + } + Section { HStack { Image("GitHub") @@ -358,12 +381,18 @@ struct LCSettingsView: View { .onChange(of: silentSwitchApp) { newValue in saveItem(key: "LCSwitchAppWithoutAsking", val: newValue) } + .onChange(of: silentOpenWebPage) { newValue in + saveItem(key: "LCOpenWebPageWithoutAsking", val: newValue) + } .onChange(of: frameShortIcon) { newValue in saveItem(key: "LCFrameShortcutIcons", val: newValue) } .onChange(of: injectToLCItelf) { newValue in saveItem(key: "LCLoadTweaksToSelf", val: newValue) } + .onChange(of: ignoreJITOnLaunch) { newValue in + saveItem(key: "LCIgnoreJITOnLaunch", val: newValue) + } .onChange(of: strictHiding) { newValue in saveAppGroupItem(key: "LCStrictHiding", val: newValue) } @@ -389,32 +418,6 @@ struct LCSettingsView: View { LCUtils.appGroupUserDefault.setValue(val, forKey: key) } - func testJITLessMode() { - if !LCUtils.isAppGroupAltStoreLike() { - errorInfo = "lc.settings.unsupportedInstallMethod".loc - errorShow = true - return; - } - - if !isAltStorePatched { - errorInfo = "lc.settings.error.storeNotPatched %@".localizeWithFormat(storeName) - errorShow = true - return; - } - isJITLessTestInProgress = true - LCUtils.validateJITLessSetup(with: defaultSigner) { success, error in - if success { - successInfo = "lc.jitlessSetup.success".loc - successShow = true - } else { - errorInfo = "lc.jitlessSetup.error.testLibLoadFailed %@ %@ %@".localizeWithFormat(storeName, storeName, storeName) + "\n" + (error?.localizedDescription ?? "") - errorShow = true - } - isJITLessTestInProgress = false - } - - } - func installAnotherLC() { if !LCUtils.isAppGroupAltStoreLike() { errorInfo = "lc.settings.unsupportedInstallMethod".loc diff --git a/LiveContainerSwiftUI/LCTabView.swift b/LiveContainerSwiftUI/LCTabView.swift index fa3c9a7..227c737 100644 --- a/LiveContainerSwiftUI/LCTabView.swift +++ b/LiveContainerSwiftUI/LCTabView.swift @@ -40,20 +40,21 @@ struct LCTabView: View { tempApps.append(LCAppModel(appInfo: newApp)) } } - - try fm.createDirectory(at: LCPath.lcGroupBundlePath, withIntermediateDirectories: true) - let appDirsShared = try fm.contentsOfDirectory(atPath: LCPath.lcGroupBundlePath.path) - for appDir in appDirsShared { - if !appDir.hasSuffix(".app") { - continue - } - let newApp = LCAppInfo(bundlePath: "\(LCPath.lcGroupBundlePath.path)/\(appDir)")! - newApp.relativeBundlePath = appDir - newApp.isShared = true - if newApp.isHidden { - tempHiddenApps.append(LCAppModel(appInfo: newApp)) - } else { - tempApps.append(LCAppModel(appInfo: newApp)) + if LCPath.lcGroupDocPath != LCPath.docPath { + try fm.createDirectory(at: LCPath.lcGroupBundlePath, withIntermediateDirectories: true) + let appDirsShared = try fm.contentsOfDirectory(atPath: LCPath.lcGroupBundlePath.path) + for appDir in appDirsShared { + if !appDir.hasSuffix(".app") { + continue + } + let newApp = LCAppInfo(bundlePath: "\(LCPath.lcGroupBundlePath.path)/\(appDir)")! + newApp.relativeBundlePath = appDir + newApp.isShared = true + if newApp.isHidden { + tempHiddenApps.append(LCAppModel(appInfo: newApp)) + } else { + tempApps.append(LCAppModel(appInfo: newApp)) + } } } // load document folders diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index 6301f99..fdd690c 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -32,18 +32,17 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error; + (NSData *)certificateData; + (NSString *)certificatePassword; -+ (BOOL)deleteKeychainItem:(NSString *)key ofStore:(NSString *)store; -+ (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store; + (BOOL)askForJIT; + (BOOL)launchToGuestApp; + (BOOL)launchToGuestAppWithURL:(NSURL *)url; + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL; -+ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; ++ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler; ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler; + (BOOL)isAppGroupAltStoreLike; + (Store)store; ++ (NSString *)teamIdentifier; + (NSString *)appGroupID; + (NSString *)appUrlScheme; + (NSURL *)appGroupPath; diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index a835150..1b68b39 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -2,11 +2,13 @@ @import MachO; @import UIKit; -#import "AltStoreCore/ALTSigner.h" +#import "../AltStoreCore/ALTSigner.h" #import "LCUtils.h" #import "LCVersionInfo.h" #import "../ZSign/zsigner.h" +Class LCSharedUtilsClass = nil; + // make SFSafariView happy and open data: URLs @implementation NSURL(hack) - (BOOL)safari_isHTTPFamilyURL { @@ -17,58 +19,27 @@ - (BOOL)safari_isHTTPFamilyURL { @implementation LCUtils -#pragma mark Certificate & password - -+ (NSURL *)appGroupPath { - return [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; ++ (void)load { + LCSharedUtilsClass = NSClassFromString(@"LCSharedUtils"); } -+ (BOOL)deleteKeychainItem:(NSString *)key ofStore:(NSString *)store { - NSDictionary *dict = @{ - (id)kSecClass: (id)kSecClassGenericPassword, - (id)kSecAttrService: store, - (id)kSecAttrAccount: key, - (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny - }; - OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict); - return status == errSecSuccess; +#pragma mark Certificate & password ++ (NSString *)teamIdentifier { + return [LCSharedUtilsClass teamIdentifier]; } -+ (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store { - NSDictionary *dict = @{ - (id)kSecClass: (id)kSecClassGenericPassword, - (id)kSecAttrService: store, - (id)kSecAttrAccount: key, - (id)kSecAttrSynchronizable: (id)kSecAttrSynchronizableAny, - (id)kSecMatchLimit: (id)kSecMatchLimitOne, - (id)kSecReturnData: (id)kCFBooleanTrue - }; - CFTypeRef result = nil; - OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); - if (status == errSecSuccess) { - return (__bridge NSData *)result; - } else { - return nil; - } ++ (NSURL *)appGroupPath { + return [LCSharedUtilsClass appGroupPath]; } + (NSData *)certificateData { NSData* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificateData"]; - if(ans) { - return ans; - } else { - return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificateData"]; - } + return ans; } + (NSString *)certificatePassword { - // password of cert retrieved from the store tweak is always @"". We just keep this function so we can check if certificate presents without changing codes. - if([[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]) { - return @""; - } else { - return nil; - } + return [LCSharedUtilsClass certificatePassword]; } + (void)setCertificatePassword:(NSString *)certPassword { @@ -76,17 +47,21 @@ + (void)setCertificatePassword:(NSString *)certPassword { [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] setObject:certPassword forKey:@"LCCertificatePassword"]; } ++ (NSString *)appGroupID { + return [LCSharedUtilsClass appGroupID]; +} + #pragma mark LCSharedUtils wrappers + (BOOL)launchToGuestApp { - return [NSClassFromString(@"LCSharedUtils") launchToGuestApp]; + return [LCSharedUtilsClass launchToGuestApp]; } + (BOOL)askForJIT { - return [NSClassFromString(@"LCSharedUtils") askForJIT]; + return [LCSharedUtilsClass askForJIT]; } + (BOOL)launchToGuestAppWithURL:(NSURL *)url { - return [NSClassFromString(@"LCSharedUtils") launchToGuestAppWithURL:url]; + return [LCSharedUtilsClass launchToGuestAppWithURL:url]; } #pragma mark Code signing @@ -129,18 +104,16 @@ + (void)loadStoreFrameworksWithError2:(NSError **)error { loaded = YES; } -+ (NSString *)storeBundleID { - // Assuming this format never changes... - // group.BUNDLEID.YOURTEAMID - return [self.appGroupID substringWithRange:NSMakeRange(6, self.appGroupID.length - 17)]; -} - + (NSURL *)storeBundlePath { - return [self.appGroupPath URLByAppendingPathComponent:[NSString stringWithFormat:@"Apps/%@/App.app", self.storeBundleID]]; + if ([self store] == SideStore) { + return [self.appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore/App.app"]; + } else { + return [self.appGroupPath URLByAppendingPathComponent:@"Apps/com.rileytestut.AltStore/App.app"]; + } } + (NSString *)storeInstallURLScheme { - if ([self.storeBundleID containsString:@"SideStore"]) { + if ([self store] == SideStore) { return @"sidestore://install?url=%@"; } else { return @"altstore://install?url=%@"; @@ -194,7 +167,7 @@ + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL { } } -+ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { ++ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler { NSError *error; // I'm too lazy to reimplement signer, so let's borrow everything from SideStore @@ -204,20 +177,20 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc // Load libraries from Documents, yeah [self loadStoreFrameworksWithError:&error]; if (error) { - completionHandler(NO, nil, error); + completionHandler(NO, nil, nil, error); return nil; } ALTCertificate *cert = [[NSClassFromString(@"ALTCertificate") alloc] initWithP12Data:self.certificateData password:self.certificatePassword]; if (!cert) { error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:1 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTCertificate. Please try: 1. make sure your store is patched 2. reopen your store 3. refresh all apps"}]; - completionHandler(NO, nil, error); + completionHandler(NO, nil, nil, error); return nil; } ALTProvisioningProfile *profile = [[NSClassFromString(@"ALTProvisioningProfile") alloc] initWithURL:profilePath]; if (!profile) { error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:2 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTProvisioningProfile. Please try: 1. make sure your store is patched 2. reopen your store 3. refresh all apps"}]; - completionHandler(NO, nil, error); + completionHandler(NO, nil, nil, error); return nil; } @@ -226,13 +199,13 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc ALTSigner *signer = [[NSClassFromString(@"ALTSigner") alloc] initWithTeam:team certificate:cert]; void (^signCompletionHandler)(BOOL success, NSError *error) = ^(BOOL success, NSError *_Nullable error) { - completionHandler(success, [profile expirationDate], error); + completionHandler(success, [profile expirationDate], [profile teamIdentifier], error); }; return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:signCompletionHandler]; } -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler { NSError *error; // use zsign as our signer~ @@ -242,7 +215,7 @@ + (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^) [self loadStoreFrameworksWithError2:&error]; if (error) { - completionHandler(NO, nil, error); + completionHandler(NO, nil, nil, error); return nil; } @@ -255,23 +228,6 @@ + (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^) #pragma mark Setup -+ (NSString *)appGroupID { - static dispatch_once_t once; - static NSString *appGroupID = @"group.com.SideStore.SideStore";; - dispatch_once(&once, ^{ - for (NSString *group in NSBundle.mainBundle.infoDictionary[@"ALTAppGroups"]) { - NSURL *path = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:group]; - NSURL *bundlePath = [path URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer/App.app"]; - if ([NSFileManager.defaultManager fileExistsAtPath:bundlePath.path]) { - // This will fail if LiveContainer is installed in both stores, but it should never be the case - appGroupID = group; - return; - } - } - }); - return appGroupID; -} - + (Store) store { static Store ans; static dispatch_once_t onceToken; @@ -343,14 +299,14 @@ + (void)validateJITLessSetupWithSigner:(Signer)signer completionHandler:(void (^ // Sign the test app bundle if(signer == AltSign) { [LCUtils signAppBundle:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { + completionHandler:^(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ completionHandler(success, error); }); }]; } else { [LCUtils signAppBundleWithZSign:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { + completionHandler:^(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ completionHandler(success, error); }); @@ -436,6 +392,14 @@ + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error { NSFileManager *manager = NSFileManager.defaultManager; NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; + if(!appGroupPath) { + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : @"Unable to access App Group. Please check JITLess diagnose page for more information." + }; + *error = [NSError errorWithDomain:@"Unable to Access App Group" code:-1 userInfo:userInfo]; + return nil; + } + NSURL *lcBundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer"]; NSURL *bundlePath; if ([self store] == SideStore) { diff --git a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj index 3816ac3..1055c60 100644 --- a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj +++ b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj @@ -24,11 +24,13 @@ 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C72C76FE3500C6C918 /* LCAppBanner.swift */; }; 173564D32C76FE3500C6C918 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173564C82C76FE3500C6C918 /* Assets.xcassets */; }; 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173F183F2C7B7B74002953AA /* LCWebView.swift */; }; + 1747D7522D2965FE005694C7 /* LCHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1747D7512D2965F3005694C7 /* LCHelpView.swift */; }; 17583D342D08555000FBA7F0 /* LCAppPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17583D332D08554400FBA7F0 /* LCAppPreferenceView.swift */; }; 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178B4C3D2C77654400DD1F74 /* Shared.swift */; }; 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179765E02CF8192200D40B95 /* AppDelegate.swift */; }; 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */; }; 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */; }; + 17EC1CE02D13BE9800A17824 /* LCJITLessDiagnoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17EC1CDF2D13BE9000A17824 /* LCJITLessDiagnoseView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -55,6 +57,7 @@ 173564C72C76FE3500C6C918 /* LCAppBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCAppBanner.swift; sourceTree = ""; }; 173564C82C76FE3500C6C918 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 173F183F2C7B7B74002953AA /* LCWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCWebView.swift; sourceTree = ""; }; + 1747D7512D2965F3005694C7 /* LCHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCHelpView.swift; sourceTree = ""; }; 17583D332D08554400FBA7F0 /* LCAppPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppPreferenceView.swift; sourceTree = ""; }; 178B4C3D2C77654400DD1F74 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LiveContainerSwiftUI-Bridging-Header.h"; sourceTree = ""; }; @@ -62,6 +65,7 @@ 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppModel.swift; sourceTree = ""; }; 17B9B88D2C760678009D079E /* LiveContainerSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveContainerSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppSettingsView.swift; sourceTree = ""; }; + 17EC1CDF2D13BE9000A17824 /* LCJITLessDiagnoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCJITLessDiagnoseView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -78,6 +82,8 @@ 173564BB2C76FE1500C6C918 /* LiveContainerSwiftUI */ = { isa = PBXGroup; children = ( + 1747D7512D2965F3005694C7 /* LCHelpView.swift */, + 17EC1CDF2D13BE9000A17824 /* LCJITLessDiagnoseView.swift */, 170D51B42D02E291009000A6 /* LCSelectContainerView.swift */, 173F183F2C7B7B74002953AA /* LCWebView.swift */, 173564C82C76FE3500C6C918 /* Assets.xcassets */, @@ -205,11 +211,13 @@ 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */, 171014242CE9B63100673269 /* LCMachOUtils.m in Sources */, 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */, + 17EC1CE02D13BE9800A17824 /* LCJITLessDiagnoseView.swift in Sources */, 170D51B52D02E29F009000A6 /* LCSelectContainerView.swift in Sources */, 171510872D01D61E00ED4B2E /* LCContainer.swift in Sources */, 171014182CE9B42000673269 /* LCVersionInfo.m in Sources */, 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */, 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */, + 1747D7522D2965FE005694C7 /* LCHelpView.swift in Sources */, 173564CE2C76FE3500C6C918 /* Makefile in Sources */, 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */, 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */, diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 74ec3d2..b5f38a4 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -773,6 +773,40 @@ } } }, + "lc.appSettings.fixBlackScreen" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fix Black Screen" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "修复黑屏" + } + } + } + }, + "lc.appSettings.fixBlackScreenDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If app shows a black screen when launching and crash after about 10s, enable this option." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果App启动时黑屏,并在10秒后崩溃,启用这个选项。" + } + } + } + }, "lc.appSettings.fixes" : { "extractionState" : "manual", "localizations" : { @@ -892,19 +926,36 @@ } } }, - "lc.appSettings.languageLoading" : { + "lc.appSettings.ignoreDlopenError" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Loading Available Languages…" + "value" : "Ignore Error During dlopen" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "正在加载可选语言" + "value" : "忽略dlopen时的错误" + } + } + } + }, + "lc.appSettings.ignoreDlopenErrorDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Might solve errors like dlsym(xxxx, xxxx): symbol not found" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "可能解决类似dlsym(xxxx,xxxx): symbol not found的问题" } } } @@ -966,7 +1017,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "New data folder" + "value" : "New Data Folder" } }, "zh_CN" : { @@ -1351,6 +1402,23 @@ } } }, + "lc.common.loading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading…" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "加载中…" + } + } + } + }, "lc.common.miscellaneous" : { "extractionState" : "manual", "localizations" : { @@ -1385,6 +1453,23 @@ } } }, + "lc.common.no" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "否" + } + } + } + }, "lc.common.none" : { "extractionState" : "manual", "localizations" : { @@ -1453,6 +1538,40 @@ } } }, + "lc.common.unknown" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知" + } + } + } + }, + "lc.common.yes" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Yes" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "是" + } + } + } + }, "lc.container.alreadyDefaultContainer" : { "extractionState" : "manual", "localizations" : { @@ -1589,6 +1708,23 @@ } } }, + "lc.container.isolateAppGroup" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isolate App Group" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "独立AppGroup" + } + } + } + }, "lc.container.notEnoughKeychainGroup" : { "extractionState" : "manual", "localizations" : { @@ -1861,6 +1997,210 @@ } } }, + "lc.helpView.text1" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Setup JITLess mode (Patch Store) in settings to use LiveContainer without JIT.\n\n**Check JITLess Diagnose page in settings if you have trouble enabling JITLess mode.**\n\nPress plus button to install apps.\n\nLong press app for app-specific settings, fixes and adding apps to homescreen." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "在设置中配置免JIT模式(打补丁)来使得LiveContainer无需JIT权限即可使用。\n\n**如果配置免JIT模式时遇到问题,请查看免JIT模式诊断页面。**\n\n点击加号按钮来安装App。\n\n长按App可查看App设置,修复App问题,以及将App添加到主屏幕。" + } + } + } + }, + "lc.helpView.text2" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "**Check [LiveContainer FAQ](https://github.com/khanhduytran0/LiveContainer/issues/265) for common issues.**\n \nIf you encounters a bug or compatibility issue please submit on [GitHub issues](https://github.com/khanhduytran0/LiveContainer/issues)." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "**遇到问题时请先查看[LiveContainer常见问题(英语)](https://github.com/khanhduytran0/LiveContainer/issues/265)。**\n \n如果你遇到Bug或者App兼容性问题,请到[GitHub issues](https://github.com/khanhduytran0/LiveContainer/issues)提交。" + } + } + } + }, + "lc.helpView.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "LiveContainer Help" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "LiveContainer帮助" + } + } + } + }, + "lc.jitlessDiag.appGroupAccessible" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Group Accessible" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Group可访问" + } + } + } + }, + "lc.jitlessDiag.appGroupId" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Group ID" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "App Group名" + } + } + } + }, + "lc.jitlessDiag.bundleId" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bundle Identifier" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "包名" + } + } + } + }, + "lc.jitlessDiag.certDataFound" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Certificate Data Found" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测到证书数据" + } + } + } + }, + "lc.jitlessDiag.certLastUpdate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Certificate Last Update Date" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "证书上次更新时间" + } + } + } + }, + "lc.jitlessDiag.certPassFound" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Certificate Password Found" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测到证书密码" + } + } + } + }, + "lc.jitlessDiag.getHelp" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Get Help" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "获取帮助" + } + } + } + }, + "lc.jitlessDiag.patchDetected" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patch Detected" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "检测到补丁" + } + } + } + }, + "lc.jitlessDiag.store" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Store" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "安装方式" + } + } + } + }, "lc.jitlessSetup.error.certDataNotFound" : { "extractionState" : "manual", "localizations" : { @@ -2231,7 +2571,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Frame Short Icon" + "value" : "Frame Shortcut Icon" } }, "zh_CN" : { @@ -2378,6 +2718,23 @@ } } }, + "lc.settings.jitlessDiagnose" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "JIT-Less Mode Diagnose" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "免JIT模式诊断" + } + } + } + }, "lc.settings.JitUDID" : { "extractionState" : "manual", "localizations" : { @@ -2785,6 +3142,40 @@ } } }, + "lc.settings.silentOpenWebPage" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Always Open URL in Current App" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "总是在当前App打开链接" + } + } + } + }, + "lc.settings.silentOpenWebPageDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "By default, LiveContainer asks you how to open an URL. Enable this automatically open URL in current app." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "LiveContainer在打开链接时会询问你如何打开。启用此选项来自动在当前App中打开。" + } + } + } + }, "lc.settings.silentSwitchApp" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 69e514b..1d0499c 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -408,7 +408,7 @@ extension LCUtils { } var ansDate : Date? = nil await withCheckedContinuation { c in - func compeletionHandler(success: Bool, expirationDate: Date?, error: Error?){ + func compeletionHandler(success: Bool, expirationDate: Date?, teamId : String?, error: Error?){ do { if let error = error { ans = error.localizedDescription @@ -638,7 +638,13 @@ extension LCUtils { } else { // Biometric authentication is not available DispatchQueue.main.async { - completion(false, error) + if let evaluationError = error as? LAError, evaluationError.code == LAError.passcodeNotSet { + // No passcode set, we also define this as successful Authentication + completion(true, nil) + } else { + completion(false, error) + } + } } } diff --git a/Makefile b/Makefile index a10a453..45407bf 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ export CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1- # Build the app APPLICATION_NAME = LiveContainer -$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtils.m NSUserDefaults.m +$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtils.m NSUserDefaults.m fishhook/fishhook.c $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e _LiveContainerMain -rpath @loader_path/Frameworks diff --git a/NSUserDefaults.m b/NSUserDefaults.m index 68ca082..64b3f17 100644 --- a/NSUserDefaults.m +++ b/NSUserDefaults.m @@ -5,9 +5,8 @@ // Created by s s on 2024/11/29. // -#import +#import "FoundationPrivate.h" #import "LCSharedUtils.h" -#import "UIKitPrivate.h" #import "utils.h" #import "LCSharedUtils.h" @@ -33,7 +32,6 @@ void NUDGuestHooksInit() { swizzle(NSUserDefaults.class, @selector(dictionaryRepresentation), @selector(hook_dictionaryRepresentation)); swizzle(NSUserDefaults.class, @selector(persistentDomainForName:), @selector(hook_persistentDomainForName:)); swizzle(NSUserDefaults.class, @selector(removePersistentDomainForName:), @selector(hook_removePersistentDomainForName:)); - swizzle(NSUserDefaults.class, @selector(registerDefaults:), @selector(hook_registerDefaults:)); LCPreferences = [[NSMutableDictionary alloc] init]; NSFileManager* fm = NSFileManager.defaultManager; NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; @@ -43,7 +41,7 @@ void NUDGuestHooksInit() { [fm createDirectoryAtPath:preferenceFolderPath.path withIntermediateDirectories:YES attributes:@{} error:&error]; } - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification + [[NSNotificationCenter defaultCenter] addObserverForName:@"UIApplicationWillTerminateNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notification) { @@ -86,10 +84,19 @@ void LCSavePreference(void) { @implementation NSUserDefaults(LiveContainerHooks) +- (NSString*)realIdentifier { + NSString* identifier = [self _identifier]; + if([identifier hasPrefix:@"com.kdt.livecontainer"]) { + return NSUserDefaults.standardUserDefaults._identifier; + } else { + return identifier; + } +} + - (id)hook_objectForKey:(NSString*)key { // let LiveContainer itself bypass - NSString* identifier = [self _identifier]; - if([identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { + NSString* identifier = [self realIdentifier]; + if(self == [NSUserDefaults lcUserDefaults]) { return [self hook_objectForKey:key]; } @@ -135,8 +142,8 @@ - (NSInteger)hook_integerForKey:(NSString*)key { - (void)hook_setObject:(id)obj forKey:(NSString*)key { // let LiveContainer itself bypess - NSString* identifier = [self _identifier]; - if([identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { + NSString* identifier = [self realIdentifier]; + if(self == [NSUserDefaults lcUserDefaults]) { return [self hook_setObject:obj forKey:key]; } @synchronized (LCPreferences) { @@ -151,7 +158,7 @@ - (void)hook_setObject:(id)obj forKey:(NSString*)key { } - (void)hook_removeObjectForKey:(NSString*)key { - NSString* identifier = [self _identifier]; + NSString* identifier = [self realIdentifier]; if([self hook_objectForKey:key]) { [self hook_removeObjectForKey:key]; return; @@ -167,7 +174,7 @@ - (void)hook_removeObjectForKey:(NSString*)key { } - (NSDictionary*) hook_dictionaryRepresentation { - NSString* identifier = [self _identifier]; + NSString* identifier = [self realIdentifier]; NSMutableDictionary* ans = [[self hook_dictionaryRepresentation] mutableCopy]; if(ans) { @synchronized (LCPreferences) { @@ -181,6 +188,10 @@ - (NSDictionary*) hook_dictionaryRepresentation { } - (NSDictionary*) hook_persistentDomainForName:(NSString*)domainName { + if([domainName hasPrefix:@"com.kdt.livecontainer"]) { + domainName = NSUserDefaults.standardUserDefaults._identifier; + } + NSMutableDictionary* ans = [[self hook_persistentDomainForName:domainName] mutableCopy]; if(ans) { @synchronized (LCPreferences) { @@ -200,7 +211,7 @@ - (void) hook_removePersistentDomainForName:(NSString*)domainName { [self hook_removePersistentDomainForName:domainName]; } else { // empty dictionary means deletion - [LCPreferences setObject:[[NSMutableArray alloc] init] forKey:domainName]; + [LCPreferences setObject:[[NSMutableDictionary alloc] init] forKey:domainName]; LCSavePreference(); } NSURL* preferenceFilePath = LCGetPreferencePath(domainName); @@ -212,21 +223,4 @@ - (void) hook_removePersistentDomainForName:(NSString*)domainName { } } -- (void) hook_registerDefaults:(NSDictionary *)registrationDictionary { - NSString* identifier = [self _identifier]; - @synchronized (LCPreferences) { - NSMutableDictionary* preferenceDict = LCGetPreference(identifier); - if(!preferenceDict) { - preferenceDict = [[NSMutableDictionary alloc] init]; - LCPreferences[identifier] = preferenceDict; - } - for(NSString* key in registrationDictionary) { - if(![preferenceDict objectForKey:key]) { - preferenceDict[key] = registrationDictionary[key]; - } - } - LCSavePreference(); - } -} - @end diff --git a/Resources/Info.plist b/Resources/Info.plist index aa7b003..54d27c4 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -42,7 +42,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.1.0 + 3.2.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -61,7 +61,7 @@ CFBundleVersion - 3.1.0 + 3.2.0 LSApplicationCategoryType public.app-category.games LSApplicationQueriesSchemes @@ -159,6 +159,7 @@ location nearby-interaction remote-notification + voip UIDeviceFamily @@ -259,12 +260,6 @@ UISupportsDocumentBrowser - UIBackgroundModes - - remote-notification - audio - - GCSupportsControllerUserInteraction GCSupportedGameControllers diff --git a/Resources/Settings.bundle/Root.plist b/Resources/Settings.bundle/Root.plist new file mode 100644 index 0000000..533fa10 --- /dev/null +++ b/Resources/Settings.bundle/Root.plist @@ -0,0 +1,17 @@ + + + + + PreferenceSpecifiers + + + Type + PSTextFieldSpecifier + Title + selected + Key + selected + + + + diff --git a/TweakLoader/DocumentPicker.m b/TweakLoader/DocumentPicker.m index df4c7bc..63237df 100644 --- a/TweakLoader/DocumentPicker.m +++ b/TweakLoader/DocumentPicker.m @@ -7,7 +7,7 @@ BOOL fixFilePicker; __attribute__((constructor)) static void NSFMGuestHooksInit() { - fixFilePicker = [NSBundle.mainBundle.infoDictionary[@"doSymlinkInbox"] boolValue]; + fixFilePicker = [NSUserDefaults.guestAppInfo[@"doSymlinkInbox"] boolValue]; swizzle(UIDocumentPickerViewController.class, @selector(initForOpeningContentTypes:asCopy:), @selector(hook_initForOpeningContentTypes:asCopy:)); swizzle(UIDocumentPickerViewController.class, @selector(initWithDocumentTypes:inMode:), @selector(hook_initWithDocumentTypes:inMode:)); diff --git a/TweakLoader/FBSSerialQueue.m b/TweakLoader/FBSSerialQueue.m index 218227b..ec00bec 100644 --- a/TweakLoader/FBSSerialQueue.m +++ b/TweakLoader/FBSSerialQueue.m @@ -26,7 +26,7 @@ - (void)setCurrentSubscription:(id)sub { __attribute__((constructor)) static void NSFMGuestHooksInit() { - if(![[NSBundle.mainBundle infoDictionary][@"bypassAssertBarrierOnQueue"] boolValue]) { + if(![NSUserDefaults.guestAppInfo[@"bypassAssertBarrierOnQueue"] boolValue]) { return; } diff --git a/TweakLoader/NSFileManager+GuestHooks.m b/TweakLoader/NSFileManager+GuestHooks.m index 8fecacb..72e2509 100644 --- a/TweakLoader/NSFileManager+GuestHooks.m +++ b/TweakLoader/NSFileManager+GuestHooks.m @@ -2,8 +2,12 @@ #import "utils.h" #import "LCSharedUtils.h" +BOOL isolateAppGroup = NO; __attribute__((constructor)) static void NSFMGuestHooksInit() { + NSString* containerInfoPath = [[NSString stringWithUTF8String:getenv("HOME")] stringByAppendingPathComponent:@"LCContainerInfo.plist"]; + NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:containerInfoPath]; + isolateAppGroup = [infoDict[@"isolateAppGroup"] boolValue]; swizzle(NSFileManager.class, @selector(containerURLForSecurityApplicationGroupIdentifier:), @selector(hook_containerURLForSecurityApplicationGroupIdentifier:)); } @@ -14,8 +18,12 @@ - (nullable NSURL *)hook_containerURLForSecurityApplicationGroupIdentifier:(NSSt if([groupIdentifier isEqualToString:[NSClassFromString(@"LCSharedUtils") appGroupID]]) { return [NSURL fileURLWithPath: NSUserDefaults.lcAppGroupPath]; } - - NSURL *result = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/LiveContainer/Data/AppGroup/%@", NSUserDefaults.lcAppGroupPath, groupIdentifier]]; + NSURL *result; + if(isolateAppGroup) { + result = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/LCAppGroup/%@", getenv("HOME"), groupIdentifier]]; + } else { + result = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/LiveContainer/Data/AppGroup/%@", NSUserDefaults.lcAppGroupPath, groupIdentifier]]; + } [NSFileManager.defaultManager createDirectoryAtURL:result withIntermediateDirectories:YES attributes:nil error:nil]; return result; } diff --git a/TweakLoader/SecItem.m b/TweakLoader/SecItem.m index 5600b75..1ea346c 100644 --- a/TweakLoader/SecItem.m +++ b/TweakLoader/SecItem.m @@ -84,8 +84,11 @@ static void SecItemGuestHooksInit() { accessGroup = [NSString stringWithFormat:@"%@.com.kdt.livecontainer.shared.%d", groupId, keychainGroupId]; } - rebind_symbols((struct rebinding[1]){{"SecItemAdd", (void *)new_SecItemAdd, (void **)&orig_SecItemAdd}},1); - rebind_symbols((struct rebinding[1]){{"SecItemCopyMatching", (void *)new_SecItemCopyMatching, (void **)&orig_SecItemCopyMatching}},1); - rebind_symbols((struct rebinding[1]){{"SecItemUpdate", (void *)new_SecItemUpdate, (void **)&orig_SecItemUpdate}},1); - rebind_symbols((struct rebinding[1]){{"SecItemDelete", (void *)new_SecItemDelete, (void **)&orig_SecItemDelete}},1); + struct rebinding rebindings[] = (struct rebinding[]){ + {"SecItemAdd", (void *)new_SecItemAdd, (void **)&orig_SecItemAdd}, + {"SecItemCopyMatching", (void *)new_SecItemCopyMatching, (void **)&orig_SecItemCopyMatching}, + {"SecItemUpdate", (void *)new_SecItemUpdate, (void **)&orig_SecItemUpdate}, + {"SecItemDelete", (void *)new_SecItemDelete, (void **)&orig_SecItemDelete} + }; + rebind_symbols(rebindings, sizeof(rebindings)/sizeof(struct rebinding)); } diff --git a/TweakLoader/TweakLoader.m b/TweakLoader/TweakLoader.m index 2697459..3eb9695 100644 --- a/TweakLoader/TweakLoader.m +++ b/TweakLoader/TweakLoader.m @@ -2,6 +2,7 @@ #import #include #include +#include "utils.h" static NSString *loadTweakAtURL(NSURL *url) { NSString *tweakPath = url.path; @@ -70,7 +71,7 @@ static void TweakLoaderConstructor() { } // Load selected tweak folder, recursively - NSString *tweakFolderName = NSBundle.mainBundle.infoDictionary[@"LCTweakFolder"]; + NSString *tweakFolderName = NSUserDefaults.guestAppInfo[@"LCTweakFolder"]; if (tweakFolderName.length > 0) { NSLog(@"Loading tweaks from the selected folder"); NSString *tweakFolder = [globalTweakFolder stringByAppendingPathComponent:tweakFolderName]; @@ -93,24 +94,4 @@ static void TweakLoaderConstructor() { showDlerrAlert(error); }); } -} - -// fix dlsym(RTLD_DEFAULT, bd_requestURLParameters): symbol not found -// by declearing a dummy funtion that generates trash data since it's just a user tracking function -// see https://github.com/volcengine/datarangers-sdk-ios/blob/7ca475f90be36016d35281a02b4e44b6f99f4c72/BDAutoTracker/Classes/Core/Network/BDAutoTrackNetworkRequest.m#L22 -NSMutableDictionary * bd_requestURLParameters(NSString *appID) { - NSMutableDictionary *result = [NSMutableDictionary new]; - [result setValue:@"ios" forKey:@"platform"]; - [result setValue:@"ios" forKey:@"sdk_lib"]; - [result setValue:@"iPhone" forKey:@"device_platform"]; - [result setValue:@(61002) forKey:@"sdk_version"]; - [result setValue:@"iOS" forKey:@"os"]; - [result setValue:@"18.0" forKey:@"os_version"]; - [result setValue:@"6.9.69" forKey:@"app_version"]; - [result setValue:@"iPhone14,2" forKey:@"device_model"]; - [result setValue:@(NO) forKey:@"is_upgrade_user"]; - [result setValue:@"00000000-0000-0000-0000-000000000000" forKey:@"idfa"]; - [result setValue:@"00000000-0000-0000-0000-000000000000" forKey:@"idfv"]; - [result setValue:@"6.9.69" forKey:@"version_code"]; - return result; -} +} \ No newline at end of file diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index a69906d..3bf020e 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -5,27 +5,30 @@ #import #import "Localization.h" -UIInterfaceOrientation orientationLock = UIInterfaceOrientationUnknown; +UIInterfaceOrientation LCOrientationLock = UIInterfaceOrientationUnknown; +NSMutableArray* LCSupportedUrlSchemes = nil; __attribute__((constructor)) static void UIKitGuestHooksInit() { swizzle(UIApplication.class, @selector(_applicationOpenURLAction:payload:origin:), @selector(hook__applicationOpenURLAction:payload:origin:)); swizzle(UIApplication.class, @selector(_connectUISceneFromFBSScene:transitionContext:), @selector(hook__connectUISceneFromFBSScene:transitionContext:)); + swizzle(UIApplication.class, @selector(openURL:options:completionHandler:), @selector(hook_openURL:options:completionHandler:)); + swizzle(UIApplication.class, @selector(canOpenURL:), @selector(hook_canOpenURL:)); swizzle(UIScene.class, @selector(scene:didReceiveActions:fromTransitionContext:), @selector(hook_scene:didReceiveActions:fromTransitionContext:)); - - NSInteger orientationLockDirection = [NSBundle.mainBundle.infoDictionary[@"LCOrientationLock"] integerValue]; + swizzle(UIScene.class, @selector(openURL:options:completionHandler:), @selector(hook_openURL:options:completionHandler:)); + NSInteger LCOrientationLockDirection = [NSUserDefaults.guestAppInfo[@"LCOrientationLock"] integerValue]; if([UIDevice.currentDevice userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { - switch (orientationLockDirection) { + switch (LCOrientationLockDirection) { case 1: - orientationLock = UIInterfaceOrientationLandscapeRight; + LCOrientationLock = UIInterfaceOrientationLandscapeRight; break; case 2: - orientationLock = UIInterfaceOrientationPortrait; + LCOrientationLock = UIInterfaceOrientationPortrait; break; default: break; } - if(orientationLock != UIInterfaceOrientationUnknown) { + if(LCOrientationLock != UIInterfaceOrientationUnknown) { swizzle(UIApplication.class, @selector(_handleDelegateCallbacksWithOptions:isSuspended:restoreState:), @selector(hook__handleDelegateCallbacksWithOptions:isSuspended:restoreState:)); swizzle(FBSSceneParameters.class, @selector(initWithXPCDictionary:), @selector(hook_initWithXPCDictionary:)); swizzle(UIViewController.class, @selector(__supportedInterfaceOrientations), @selector(hook___supportedInterfaceOrientations)); @@ -42,12 +45,12 @@ static void UIKitGuestHooksInit() { NSString *appGroupPath = [NSUserDefaults lcAppGroupPath]; NSString* appGroupFolder = [appGroupPath stringByAppendingPathComponent:@"LiveContainer"]; - NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", appGroupFolder, bundleId]; + NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/LCAppInfo.plist", appGroupFolder, bundleId]; NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; if(!infoDict) { NSString* lcDocFolder = [[NSString stringWithUTF8String:getenv("LC_HOME_PATH")] stringByAppendingPathComponent:@"Documents"]; - bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", lcDocFolder, bundleId]; + bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/LCAppInfo.plist", lcDocFolder, bundleId]; infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; } @@ -138,6 +141,11 @@ void openUniversalLink(NSString* decodedUrl) { } void LCOpenWebPage(NSString* webPageUrlString, NSString* originalUrl) { + if ([NSUserDefaults.lcUserDefaults boolForKey:@"LCOpenWebPageWithoutAsking"]) { + openUniversalLink(webPageUrlString); + return; + } + NSString *message = @"lc.guestTweak.openWebPageTip".loc; UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; @@ -196,7 +204,11 @@ void authenticateUser(void (^completion)(BOOL success, NSError *error)) { }]; } else { dispatch_async(dispatch_get_main_queue(), ^{ - completion(NO, error); + if([error code] == LAErrorPasscodeNotSet) { + completion(YES, nil); + } else { + completion(NO, error); + } }); } } @@ -235,9 +247,14 @@ void handleLiveContainerLaunch(NSURL* url) { } NSBundle* bundle = [NSClassFromString(@"LCSharedUtils") findBundleWithBundleId: bundleName]; - if(!bundle || ([bundle.infoDictionary[@"isHidden"] boolValue] && [NSUserDefaults.lcSharedDefaults boolForKey:@"LCStrictHiding"])) { + NSDictionary* lcAppInfo; + if(bundle) { + lcAppInfo = [NSDictionary dictionaryWithContentsOfURL:[bundle URLForResource:@"LCAppInfo" withExtension:@"plist"]]; + } + + if(!bundle || ([lcAppInfo[@"isHidden"] boolValue] && [NSUserDefaults.lcSharedDefaults boolForKey:@"LCStrictHiding"])) { LCShowAppNotFoundAlert(bundleName); - } else if ([bundle.infoDictionary[@"isLocked"] boolValue]) { + } else if ([lcAppInfo[@"isLocked"] boolValue]) { // need authentication authenticateUser(^(BOOL success, NSError *error) { if (success) { @@ -258,6 +275,22 @@ void handleLiveContainerLaunch(NSURL* url) { } } +BOOL canAppOpenItself(NSURL* url) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; + NSArray *urlTypes = [infoDictionary objectForKey:@"CFBundleURLTypes"]; + LCSupportedUrlSchemes = [[NSMutableArray alloc] init]; + for (NSDictionary *urlType in urlTypes) { + NSArray *schemes = [urlType objectForKey:@"CFBundleURLSchemes"]; + for(NSString* scheme in schemes) { + [LCSupportedUrlSchemes addObject:[scheme lowercaseString]]; + } + } + }); + return [LCSupportedUrlSchemes containsObject:[url.scheme lowercaseString]]; +} + // Handler for AppDelegate @implementation UIApplication(LiveContainerHook) - (void)hook__applicationOpenURLAction:(id)action payload:(NSDictionary *)payload origin:(id)origin { @@ -323,6 +356,26 @@ -(BOOL)hook__handleDelegateCallbacksWithOptions:(id)arg1 isSuspended:(BOOL)arg2 return ans; } + +- (void)hook_openURL:(NSURL *)url options:(NSDictionary *)options completionHandler:(void (^)(_Bool))completion { + if(canAppOpenItself(url)) { + NSData *data = [url.absoluteString dataUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedUrl = [data base64EncodedStringWithOptions:0]; + NSString* finalUrlStr = [NSString stringWithFormat:@"%@://open-url?url=%@", NSUserDefaults.lcAppUrlScheme, encodedUrl]; + NSURL* finalUrl = [NSURL URLWithString:finalUrlStr]; + [self hook_openURL:finalUrl options:options completionHandler:completion]; + } else { + [self hook_openURL:url options:options completionHandler:completion]; + } +} +- (BOOL)hook_canOpenURL:(NSURL *) url { + if(canAppOpenItself(url)) { + return YES; + } else { + return [self hook_canOpenURL:url]; + } +} + @end // Handler for SceneDelegate @@ -382,6 +435,18 @@ - (void)hook_scene:(id)scene didReceiveActions:(NSSet *)actions fromTransitionCo [newActions removeObject:urlAction]; [self hook_scene:scene didReceiveActions:newActions fromTransitionContext:context]; } + +- (void)hook_openURL:(NSURL *)url options:(UISceneOpenExternalURLOptions *)options completionHandler:(void (^)(BOOL success))completion { + if(canAppOpenItself(url)) { + NSData *data = [url.absoluteString dataUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedUrl = [data base64EncodedStringWithOptions:0]; + NSString* finalUrlStr = [NSString stringWithFormat:@"%@://open-url?url=%@", NSUserDefaults.lcAppUrlScheme, encodedUrl]; + NSURL* finalUrl = [NSURL URLWithString:finalUrlStr]; + [self hook_openURL:finalUrl options:options completionHandler:completion]; + } else { + [self hook_openURL:url options:options completionHandler:completion]; + } +} @end @implementation FBSSceneParameters(LiveContainerHook) @@ -390,8 +455,8 @@ - (instancetype)hook_initWithXPCDictionary:(NSDictionary*)dict { FBSSceneParameters* ans = [self hook_initWithXPCDictionary:dict]; UIMutableApplicationSceneSettings* settings = [ans.settings mutableCopy]; UIMutableApplicationSceneClientSettings* clientSettings = [ans.clientSettings mutableCopy]; - [settings setInterfaceOrientation:orientationLock]; - [clientSettings setInterfaceOrientation:orientationLock]; + [settings setInterfaceOrientation:LCOrientationLock]; + [clientSettings setInterfaceOrientation:LCOrientationLock]; ans.settings = settings; ans.clientSettings = clientSettings; return ans; @@ -403,7 +468,7 @@ - (instancetype)hook_initWithXPCDictionary:(NSDictionary*)dict { @implementation UIViewController(LiveContainerHook) - (UIInterfaceOrientationMask)hook___supportedInterfaceOrientations { - if(orientationLock == UIInterfaceOrientationLandscapeRight) { + if(LCOrientationLock == UIInterfaceOrientationLandscapeRight) { return UIInterfaceOrientationMaskLandscape; } else { return UIInterfaceOrientationMaskPortrait; diff --git a/TweakLoader/utils.h b/TweakLoader/utils.h index 2556414..9f78500 100644 --- a/TweakLoader/utils.h +++ b/TweakLoader/utils.h @@ -10,4 +10,5 @@ void swizzle(Class class, SEL originalAction, SEL swizzledAction); + (NSString *)lcAppUrlScheme; + (NSString *)lcAppGroupPath; + (NSBundle *)lcMainBundle; ++ (NSDictionary*)guestAppInfo; @end diff --git a/UIKitPrivate.h b/UIKitPrivate.h index 67063f3..f23c4df 100644 --- a/UIKitPrivate.h +++ b/UIKitPrivate.h @@ -1,14 +1,5 @@ #import -@interface NSBundle(private) -- (id)_cfBundle; -@end - -@interface NSUserDefaults(private) -+ (void)setStandardUserDefaults:(id)defaults; -- (NSString*)_identifier; -@end - @interface UIImage(private) - (UIImage *)_imageWithSize:(CGSize)size; @end diff --git a/ZSign/bundle.cpp b/ZSign/bundle.cpp index 8bcfd59..fea0de4 100644 --- a/ZSign/bundle.cpp +++ b/ZSign/bundle.cpp @@ -397,7 +397,9 @@ bool ZAppBundle::SignNode(JValue &jvNode) if (!macho.Init(strExePath.c_str())) { ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); - return false; + signFailedFiles += strExePath; + signFailedFiles += "\n"; + return true; } RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); diff --git a/ZSign/zsign.hpp b/ZSign/zsign.hpp index 393e08d..365f78f 100644 --- a/ZSign/zsign.hpp +++ b/ZSign/zsign.hpp @@ -32,7 +32,7 @@ void zsign(NSString *appPath, NSData *key, NSString *pass, NSProgress* progress, - void(^completionHandler)(BOOL success, NSDate* expirationDate, NSError *error) + void(^completionHandler)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error) ); diff --git a/ZSign/zsign.mm b/ZSign/zsign.mm index 93dc896..3f2130f 100644 --- a/ZSign/zsign.mm +++ b/ZSign/zsign.mm @@ -160,7 +160,7 @@ void zsign(NSString *appPath, NSData *key, NSString *pass, NSProgress* progress, - void(^completionHandler)(BOOL success, NSDate* expirationDate, NSError *error) + void(^completionHandler)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error) ) { ZTimer gtimer; @@ -185,16 +185,17 @@ void zsign(NSString *appPath, string strPath = [appPath cStringUsingEncoding:NSUTF8StringEncoding]; - bool _ = ZLog::logs.empty(); + ZLog::logs.clear(); __block ZSignAsset zSignAsset; if (!zSignAsset.InitSimple(strPKeyFileData, (int)[key length], strProvFileData, (int)[prov length], strPassword)) { - completionHandler(NO, nil, makeErrorFromLog(ZLog::logs)); - bool _ = ZLog::logs.empty(); + completionHandler(NO, nil, nil, makeErrorFromLog(ZLog::logs)); + ZLog::logs.clear(); return; } NSDate *date = [NSDate dateWithTimeIntervalSince1970:zSignAsset.expirationDate]; + NSString* teamId = [NSString stringWithUTF8String:zSignAsset.m_strTeamId.c_str()]; bool bEnableCache = true; string strFolder = strPath; @@ -203,8 +204,8 @@ void zsign(NSString *appPath, bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); if(!success) { - completionHandler(NO, nil, makeErrorFromLog(ZLog::logs)); - bool _ = ZLog::logs.empty(); + completionHandler(NO, nil, nil,makeErrorFromLog(ZLog::logs)); + ZLog::logs.clear(); return; } @@ -229,8 +230,8 @@ void zsign(NSString *appPath, signError = [NSError errorWithDomain:@"Failed to Sign" code:-1 userInfo:userInfo]; } - completionHandler(YES, date, signError); - _ = ZLog::logs.empty(); + completionHandler(YES, date, teamId, signError); + ZLog::logs.clear(); return; } diff --git a/ZSign/zsigner.h b/ZSign/zsigner.h index 2aa227e..7dc8347 100644 --- a/ZSign/zsigner.h +++ b/ZSign/zsigner.h @@ -7,5 +7,5 @@ #import @interface ZSigner : NSObject -+ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; ++ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler; @end diff --git a/ZSign/zsigner.m b/ZSign/zsigner.m index 3efaa2f..c4b00bb 100644 --- a/ZSign/zsigner.m +++ b/ZSign/zsigner.m @@ -11,7 +11,8 @@ NSProgress* currentZSignProgress; @implementation ZSigner -+ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { ++ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass + completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSString* teamId, NSError *error))completionHandler { NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ zsign(appPath, prov, key, pass, ans, completionHandler); diff --git a/control b/control index 51d3489..5b1d900 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: com.kdt.livecontainer Name: livecontainer -Version: 3.1.0 +Version: 3.2.0 Architecture: iphoneos-arm Description: Run iOS app without actually installing it! Maintainer: khanhduytran0 diff --git a/main.m b/main.m index c115ee0..8bf0bd4 100644 --- a/main.m +++ b/main.m @@ -1,4 +1,4 @@ -#import +#import "FoundationPrivate.h" #import "LCSharedUtils.h" #import "UIKitPrivate.h" #import "utils.h" @@ -14,6 +14,8 @@ #include #include #include "TPRO.h" +#include "fishhook/fishhook.h" +#include static int (*appMain)(int, char**); static const char *dyldImageName; @@ -22,6 +24,7 @@ NSString *lcAppGroupPath; NSString* lcAppUrlScheme; NSBundle* lcMainBundle; +NSDictionary* guestAppInfo; void NUDGuestHooksInit(); @@ -41,9 +44,15 @@ + (NSString *)lcAppUrlScheme { + (NSBundle *)lcMainBundle { return lcMainBundle; } ++ (NSDictionary *)guestAppInfo { + return guestAppInfo; +} @end static BOOL checkJITEnabled() { + if([lcUserDefaults boolForKey:@"LCIgnoreJITOnLaunch"]) { + return NO; + } // check if jailbroken if (access("/var/mobile", R_OK) == 0) { return YES; @@ -193,6 +202,20 @@ static void overwriteExecPath(NSString *bundlePath) { return (void *)header + entryoff; } +uint32_t appMainImageIndex = 0; +void* appExecutableHandle = 0; +void* (*orig_dlsym)(void * __handle, const char * __symbol); +void* new_dlsym(void * __handle, const char * __symbol) { + if(__handle == (void*)RTLD_MAIN_ONLY) { + if(strcmp(__symbol, MH_EXECUTE_SYM) == 0) { + return (void*)_dyld_get_image_header(appMainImageIndex); + } + return orig_dlsym(appExecutableHandle, __symbol); + } + + return orig_dlsym(__handle, __symbol); +} + static NSString* invokeAppMain(NSString *selectedApp, NSString *selectedContainer, int argc, char *argv[]) { NSString *appError = nil; if (!LCSharedUtils.certificatePassword) { @@ -201,7 +224,7 @@ static void overwriteExecPath(NSString *bundlePath) { usleep(1000*100); } if (!checkJITEnabled()) { - appError = @"JIT was not enabled"; + appError = @"JIT was not enabled. If you want to use LiveContainer without JIT, setup JITLess mode in settings."; return appError; } } @@ -224,6 +247,7 @@ static void overwriteExecPath(NSString *bundlePath) { appBundle = [[NSBundle alloc] initWithPath:bundlePath]; isSharedBundle = true; } + guestAppInfo = [NSDictionary dictionaryWithContentsOfURL:[appBundle URLForResource:@"LCAppInfo" withExtension:@"plist"]]; if(!appBundle) { return @"App not found"; @@ -232,7 +256,7 @@ static void overwriteExecPath(NSString *bundlePath) { // find container in Info.plist NSString* dataUUID = selectedContainer; if(!dataUUID) { - dataUUID = appBundle.infoDictionary[@"LCDataUUID"]; + dataUUID = guestAppInfo[@"LCDataUUID"]; } if(dataUUID == nil) { @@ -285,8 +309,8 @@ static void overwriteExecPath(NSString *bundlePath) { overwriteExecPath(appBundle.bundlePath); // Overwrite NSUserDefaults - if([appBundle.infoDictionary[@"doUseLCBundleId"] boolValue]) { - NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.infoDictionary[@"LCOrignalBundleIdentifier"]]; + if([guestAppInfo[@"doUseLCBundleId"] boolValue]) { + NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:guestAppInfo[@"LCOrignalBundleIdentifier"]]; } else { NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; } @@ -310,7 +334,7 @@ static void overwriteExecPath(NSString *bundlePath) { remove(newTmpPath.UTF8String); symlink(getenv("TMPDIR"), newTmpPath.UTF8String); - if([appBundle.infoDictionary[@"doSymlinkInbox"] boolValue]) { + if([guestAppInfo[@"doSymlinkInbox"] boolValue]) { NSString* inboxSymlinkPath = [NSString stringWithFormat:@"%s/%@-Inbox", getenv("TMPDIR"), [appBundle bundleIdentifier]]; NSString* inboxPath = [newHomePath stringByAppendingPathComponent:@"Inbox"]; @@ -340,6 +364,11 @@ static void overwriteExecPath(NSString *bundlePath) { } } + + if([guestAppInfo[@"fixBlackScreen"] boolValue]) { + dlopen("/System/Library/Frameworks/UIKit.framework/UIKit", RTLD_GLOBAL); + NSLog(@"[LC] Fix BlackScreen2 %@", [NSClassFromString(@"UIScreen") mainScreen]); + } setenv("CFFIXED_USER_HOME", newHomePath.UTF8String, 1); setenv("HOME", newHomePath.UTF8String, 1); @@ -378,9 +407,12 @@ static void overwriteExecPath(NSString *bundlePath) { // Preload executable to bypass RT_NOLOAD uint32_t appIndex = _dyld_image_count(); + appMainImageIndex = appIndex; void *appHandle = dlopen(*path, RTLD_LAZY|RTLD_GLOBAL|RTLD_FIRST); + appExecutableHandle = appHandle; const char *dlerr = dlerror(); - if (!appHandle || (uint64_t)appHandle > 0xf00000000000 || dlerr) { + + if (!appHandle || (uint64_t)appHandle > 0xf00000000000 || (dlerr && ![guestAppInfo[@"ignoreDlopenError"] boolValue]) ) { if (dlerr) { appError = @(dlerr); } else { @@ -390,6 +422,9 @@ static void overwriteExecPath(NSString *bundlePath) { *path = oldPath; return appError; } + // hook dlsym to solve RTLD_MAIN_ONLY + rebind_symbols((struct rebinding[1]){{"dlsym", (void *)new_dlsym, (void **)&orig_dlsym}},1); + // Fix dynamic properties of some apps [NSUserDefaults performSelector:@selector(initialize)]; @@ -425,13 +460,13 @@ static void exceptionHandler(NSException *exception) { int LiveContainerMain(int argc, char *argv[]) { // This strangely fixes some apps getting stuck on black screen - NSLog(@"Ignore this: %@", UIScreen.mainScreen); + NSLog(@"Ignore this: %@", dispatch_get_main_queue()); + lcMainBundle = [NSBundle mainBundle]; lcUserDefaults = NSUserDefaults.standardUserDefaults; lcSharedDefaults = [[NSUserDefaults alloc] initWithSuiteName: [LCSharedUtils appGroupID]]; lcAppUrlScheme = NSBundle.mainBundle.infoDictionary[@"CFBundleURLTypes"][0][@"CFBundleURLSchemes"][0]; lcAppGroupPath = [[NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[NSClassFromString(@"LCSharedUtils") appGroupID]] path]; - lcMainBundle = [NSBundle mainBundle]; NSString* lastLaunchDataUUID = [lcUserDefaults objectForKey:@"lastLaunchDataUUID"]; if(lastLaunchDataUUID) { @@ -475,8 +510,8 @@ int LiveContainerMain(int argc, char *argv[]) { } NSURL* url = [NSURL URLWithString:urlStr]; - if([[UIApplication sharedApplication] canOpenURL:url]){ - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + if([[NSClassFromString(@"UIApplication") sharedApplication] canOpenURL:url]){ + [[NSClassFromString(@"UIApplication") sharedApplication] openURL:url options:@{} completionHandler:nil]; NSString *launchUrl = [lcUserDefaults stringForKey:@"launchAppUrlScheme"]; // also pass url scheme to another lc @@ -490,7 +525,7 @@ int LiveContainerMain(int argc, char *argv[]) { NSString* finalUrl = [NSString stringWithFormat:@"%@://open-url?url=%@", runningLC, encodedUrl]; NSURL* url = [NSURL URLWithString: finalUrl]; - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + [[NSClassFromString(@"UIApplication") sharedApplication] openURL:url options:@{} completionHandler:nil]; } } else { @@ -518,7 +553,7 @@ int LiveContainerMain(int argc, char *argv[]) { NSString* finalUrl = [NSString stringWithFormat:@"%@://open-url?url=%@", lcAppUrlScheme, encodedUrl]; NSURL* url = [NSURL URLWithString: finalUrl]; - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; + [[NSClassFromString(@"UIApplication") sharedApplication] openURL:url options:@{} completionHandler:nil]; }); } NSSetUncaughtExceptionHandler(&exceptionHandler); @@ -547,6 +582,9 @@ int LiveContainerMain(int argc, char *argv[]) { if ([lcUserDefaults boolForKey:@"LCLoadTweaksToSelf"]) { dlopen("@executable_path/Frameworks/TweakLoader.dylib", RTLD_LAZY); } + + void *uikitHandle = dlopen("/System/Library/Frameworks/UIKit.framework/UIKit", RTLD_GLOBAL); + int (*UIApplicationMain)(int, char**, NSString *, NSString *) = dlsym(uikitHandle, "UIApplicationMain"); return UIApplicationMain(argc, argv, nil, @"LiveContainerSwiftUI.AppDelegate"); } }