Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate LiveContainerUI to SwiftUI, Support for Multiple LiveContainer, SideJIT/JITStreamer Support, Better Open in App Support, Impelement #112 #153

Merged
merged 38 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b4d4575
swift ui proof of conecpt
hugeBlack Aug 22, 2024
33089d3
install, uninstall, run
hugeBlack Aug 23, 2024
690d45f
Create folder until app launch
hugeBlack Aug 24, 2024
efa48dd
data &tweak folder choosing
hugeBlack Aug 24, 2024
19e7a47
user can now input url scheme
hugeBlack Aug 25, 2024
81a3bb6
allow open url by universal links
hugeBlack Aug 26, 2024
48a3085
apps can be signed again if signature is invalid
hugeBlack Aug 27, 2024
7065585
Settings & JitLess setup
hugeBlack Aug 28, 2024
bd2f05a
substitute async for all semaphores
hugeBlack Aug 28, 2024
cbc1af0
tweak view
hugeBlack Aug 28, 2024
624c2d6
install tweak
hugeBlack Aug 29, 2024
b56028e
tweak signing
hugeBlack Aug 29, 2024
d50413b
multi live containers proof of concept
hugeBlack Sep 3, 2024
97894ff
open apps in multiple lcs
hugeBlack Sep 3, 2024
1f37200
key chain sharing between LCs
hugeBlack Sep 3, 2024
50ced20
fix guesthooks not working
hugeBlack Sep 3, 2024
9b116ae
Merge branch 'main' into swiftui
hugeBlack Sep 3, 2024
94a3160
Merge pull request #1 from hugeBlack/swiftui
hugeBlack Sep 3, 2024
4f45520
fix merge issue & set an icon
hugeBlack Sep 3, 2024
3ea4bb4
symlink preferences & app share lock
hugeBlack Sep 4, 2024
a276bde
launch redirect & lc2 restriction & bug fixes
hugeBlack Sep 5, 2024
48a12a8
fix preferences & installation issues
hugeBlack Sep 5, 2024
cb2dd17
being able to move dangling folders out of app group
hugeBlack Sep 6, 2024
1ddcbbe
users can mark app as JIT needed
hugeBlack Sep 10, 2024
33c98d4
support SideJITServer
hugeBlack Sep 10, 2024
5e9d119
users can hide apps
hugeBlack Sep 11, 2024
3b48909
bring back AppClip icon
hugeBlack Sep 12, 2024
186f117
use fileExporter to export app icon, solve compile warnings
hugeBlack Sep 12, 2024
901282b
Ask for authentication in guest tweak & bug fix
hugeBlack Sep 12, 2024
4d836b7
bug fix, open data folder, change app group folder to shared folder
hugeBlack Sep 13, 2024
cd2c019
fix GitHub build not packing Assets.car (#2)
hugeBlack Sep 13, 2024
cafde56
fix file picker issue, fix app crash after install, rearrange app man…
hugeBlack Sep 15, 2024
41cab66
Users can select whether to fix file picker or not
hugeBlack Sep 16, 2024
9ec2ba6
app settings view, fix #155
hugeBlack Sep 16, 2024
dc02e91
fix startAccessingSecurityScopedResource return false and not able to…
hugeBlack Sep 17, 2024
345097c
bugfix
hugeBlack Sep 17, 2024
ae901bb
Localization support & Simplified Chinese translation
hugeBlack Sep 18, 2024
a58e557
bugfix & code style improvement
hugeBlack Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.theos/
packages/
.DS_Store
LiveContainer.xcodeproj
project.xcworkspace
xcuserdata
10 changes: 9 additions & 1 deletion LCSharedUtils.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
@import Foundation;

@interface LCSharedUtils : NSObject

+ (NSString *)appGroupID;
+ (NSString *)certificatePassword;
+ (BOOL)askForJIT;
+ (BOOL)launchToGuestApp;
+ (BOOL)launchToGuestAppWithURL:(NSURL *)url;
+ (void)setWebPageUrlForNextLaunch:(NSString*)urlString;
+ (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId;
+ (void)setAppRunningByThisLC:(NSString*)bundleId;
+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo;
+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom;
+ (void)moveSharedAppFolderBack;
+ (void)removeAppRunningByLC:(NSString*)LCScheme;
+ (NSBundle*)findBundleWithBundleId:(NSString*)bundleId;
@end
258 changes: 251 additions & 7 deletions LCSharedUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,34 @@
#import "UIKitPrivate.h"

extern NSUserDefaults *lcUserDefaults;
extern NSString *lcAppUrlScheme;

@implementation LCSharedUtils

+ (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;
}

+ (NSString *)certificatePassword {
return [lcUserDefaults objectForKey:@"LCCertificatePassword"];
NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"];
if(ans) {
return ans;
} else {
return [lcUserDefaults objectForKey:@"LCCertificatePassword"];
}
}

+ (BOOL)launchToGuestApp {
Expand All @@ -16,7 +40,7 @@ + (BOOL)launchToGuestApp {
urlScheme = @"apple-magnifier://enable-jit?bundle-id=%@";
} else if (self.certificatePassword) {
tries = 8;
urlScheme = @"livecontainer://livecontainer-relaunch";
urlScheme = [NSString stringWithFormat:@"%@://livecontainer-relaunch", lcAppUrlScheme];
} else {
urlScheme = @"sidestore://sidejit-enable?bid=%@";
}
Expand All @@ -33,24 +57,244 @@ + (BOOL)launchToGuestApp {
return NO;
}

+ (BOOL)askForJIT {
NSString *urlScheme;
NSString *tsPath = [NSString stringWithFormat:@"%@/../_TrollStore", NSBundle.mainBundle.bundlePath];
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];
[LCSharedUtils launchToGuestApp];
return YES;
}
} else {
NSUserDefaults* groupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]];

NSString* sideJITServerAddress = [groupUserDefaults objectForKey:@"LCSideJITServerAddress"];
NSString* deviceUDID = [groupUserDefaults objectForKey:@"LCDeviceUDID"];
if (!sideJITServerAddress || !deviceUDID) {
return NO;
}
NSString* launchJITUrlStr = [NSString stringWithFormat: @"%@/%@/%@", sideJITServerAddress, deviceUDID, NSBundle.mainBundle.bundleIdentifier];
NSURLSession* session = [NSURLSession sharedSession];
NSURL* launchJITUrl = [NSURL URLWithString:launchJITUrlStr];
NSURLRequest* req = [[NSURLRequest alloc] initWithURL:launchJITUrl];
NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error) {
NSLog(@"[LC] failed to contact SideJITServer: %@", error);
}
}];
[task resume];

}
return NO;
}

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

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

// Attempt to restart LiveContainer with the selected guest app
return [self launchToGuestApp];
break;
launchBundleId = queryItem.value;
} else if ([queryItem.name isEqualToString:@"open-url"]){
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:queryItem.value options:0];
openUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
}
}
if(launchBundleId) {
if (openUrl) {
[lcUserDefaults setObject:openUrl forKey:@"launchAppUrlScheme"];
}

// Attempt to restart LiveContainer with the selected guest app
[lcUserDefaults setObject:launchBundleId forKey:@"selected"];
return [self launchToGuestApp];
}

return NO;
}

+ (void)setWebPageUrlForNextLaunch:(NSString*) urlString {
[lcUserDefaults setObject:urlString forKey:@"webPageToOpen"];
}

+ (NSURL*)appLockPath {
static dispatch_once_t once;
static NSURL *infoPath;

dispatch_once(&once, ^{
NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]];
infoPath = [appGroupPath URLByAppendingPathComponent:@"LiveContainer/appLock.plist"];
});
return infoPath;
}

+ (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId {
NSURL* infoPath = [self appLockPath];
NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return nil;
}

for (NSString* key in info) {
if([bundleId isEqualToString:info[key]]) {
if([key isEqualToString:lcAppUrlScheme]) {
return nil;
}
return key;
}
}

return nil;
}

// if you pass null then remove this lc from appLock
+ (void)setAppRunningByThisLC:(NSString*)bundleId {
NSURL* infoPath = [self appLockPath];

NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
info = [NSMutableDictionary new];
}
if(bundleId == nil) {
[info removeObjectForKey:lcAppUrlScheme];
} else {
info[lcAppUrlScheme] = bundleId;
}
[info writeToFile:infoPath.path atomically:YES];

}

+ (void)removeAppRunningByLC:(NSString*)LCScheme {
NSURL* infoPath = [self appLockPath];

NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path];
if (!info) {
return;
}
[info removeObjectForKey:LCScheme];
[info writeToFile:infoPath.path atomically:YES];

}

// move all plists file from fromPath to toPath
+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo {
NSFileManager* fm = [[NSFileManager alloc] init];
NSError* error1;
NSArray<NSString *> * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1];

// remove all plists in toPath first
NSArray *directoryContents = [fm contentsOfDirectoryAtPath:plistLocationTo error:&error1];
for (NSString *item in directoryContents) {
// Check if the item is a plist and does not contain "LiveContainer"
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString *itemPath = [plistLocationTo stringByAppendingPathComponent:item];
// Attempt to delete the file
[fm removeItemAtPath:itemPath error:&error1];
}

[fm createDirectoryAtPath:plistLocationTo withIntermediateDirectories:YES attributes:@{} error:&error1];
// move all plists in fromPath to toPath
for (NSString* item in plists) {
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString* toPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationTo, item];
NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item];

[fm moveItemAtPath:fromPlistPath toPath:toPlistPath error:&error1];
if(error1) {
NSLog(@"[LC] error1 = %@", error1.description);
}

}

}

// to make apple happy and prevent, we have to load all preferences into NSUserDefault so that guest app can read them
+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom {
NSFileManager* fm = [[NSFileManager alloc] init];
NSError* error1;
NSArray<NSString *> * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1];

// move all plists in fromPath to toPath
for (NSString* item in plists) {
if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) {
continue;
}
NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item];
// load, the file and sync
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithContentsOfFile:fromPlistPath];
NSUserDefaults* nud = [[NSUserDefaults alloc] initWithSuiteName: [item substringToIndex:[item length]-6]];
for(NSString* key in dict) {
[nud setObject:dict[key] forKey:key];
}

[nud synchronize];

}

}

// move app data to private folder to prevent 0xdead10cc https://forums.developer.apple.com/forums/thread/126438
+ (void)moveSharedAppFolderBack {
NSFileManager *fm = NSFileManager.defaultManager;
NSURL *libraryPathUrl = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask]
.lastObject;
NSURL *docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]
.lastObject;
NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]];
NSURL *appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"];

NSError *error;
NSString *sharedAppDataFolderPath = [libraryPathUrl.path stringByAppendingPathComponent:@"SharedDocuments"];
if(![fm fileExistsAtPath:sharedAppDataFolderPath]){
[fm createDirectoryAtPath:sharedAppDataFolderPath withIntermediateDirectories:YES attributes:@{} error:&error];
}
// move all apps in shared folder back
NSArray<NSString *> * sharedDataFoldersToMove = [fm contentsOfDirectoryAtPath:sharedAppDataFolderPath error:&error];
for(int i = 0; i < [sharedDataFoldersToMove count]; ++i) {
NSString* destPath = [appGroupFolder.path stringByAppendingPathComponent:[NSString stringWithFormat:@"Data/Application/%@", sharedDataFoldersToMove[i]]];
if([fm fileExistsAtPath:destPath]) {
[fm
moveItemAtPath:[sharedAppDataFolderPath stringByAppendingPathComponent:sharedDataFoldersToMove[i]]
toPath:[docPathUrl.path stringByAppendingPathComponent:[NSString stringWithFormat:@"FOLDER_EXISTS_AT_APP_GROUP_%@", sharedDataFoldersToMove[i]]]
error:&error
];

} else {
[fm
moveItemAtPath:[sharedAppDataFolderPath stringByAppendingPathComponent:sharedDataFoldersToMove[i]]
toPath:destPath
error:&error
];
}
}

}

+ (NSBundle*)findBundleWithBundleId:(NSString*)bundleId {
NSString *docPath = [NSString stringWithFormat:@"%s/Documents", getenv("LC_HOME_PATH")];

NSURL *appGroupFolder = nil;

NSString *bundlePath = [NSString stringWithFormat:@"%@/Applications/%@", docPath, 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"];

bundlePath = [NSString stringWithFormat:@"%@/Applications/%@", appGroupFolder.path, bundleId];
appBundle = [[NSBundle alloc] initWithPath:bundlePath];
}
return appBundle;
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "158",
"red" : "64"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xFF",
"green" : "0xF5",
"red" : "0xEC"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x66",
"green" : "0x3F",
"red" : "0x1A"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading