Skip to content

Commit

Permalink
Add builtin tweak signer & more refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
khanhduytran0 committed Aug 7, 2024
1 parent 350b99d commit 191d9ab
Show file tree
Hide file tree
Showing 12 changed files with 685 additions and 40 deletions.
56 changes: 26 additions & 30 deletions LCAppListViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,6 @@
#import "UIViewController+LCAlert.h"
#import "unarchive.h"

/*
#include <libgen.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <sys/mman.h>
#include <sys/stat.h>
*/

@implementation NSURL(hack)
- (BOOL)safari_isHTTPFamilyURL {
// Screw it, Apple
Expand Down Expand Up @@ -294,31 +286,36 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa
return 80.0f;
}

- (void)deleteAppAtIndexPath:(NSIndexPath *)indexPath {
LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath: [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]]];
UIAlertController* uninstallAlert = [UIAlertController alertControllerWithTitle:@"Confirm Uninstallation" message:[NSString stringWithFormat:@"Are you sure you want to uninstall %@?", [appInfo displayName]] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* uninstallApp = [UIAlertAction actionWithTitle:@"Uninstall" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) {
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]] error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
return;
- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^)(BOOL actionPerformed))handler {
NSString *path = [self.bundlePath stringByAppendingPathComponent:self.objects[indexPath.row]];
LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath:path];
[self showConfirmationDialogTitle:@"Confirm Uninstallation"
message:[NSString stringWithFormat:@"Are you sure you want to uninstall %@?", appInfo.displayName]
destructive:YES
confirmButtonTitle:@"Uninstall"
handler:^(UIAlertAction * action) {
if (action.style != UIAlertActionStyleCancel) {
NSError *error = nil;
[NSFileManager.defaultManager removeItemAtPath:path error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
} else {
[self.objects removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
[self.objects removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
handler(YES);
}];
[uninstallAlert addAction:uninstallApp];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[uninstallAlert addAction:cancelAction];
[self presentViewController:uninstallAlert animated:YES completion:nil];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[self deleteAppAtIndexPath:indexPath];
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return self.objects[indexPath.row].length==0 ? UITableViewCellEditingStyleNone : UITableViewCellEditingStyleDelete;
- (UISwipeActionsConfiguration *) tableView:(UITableView *)tableView
trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
return [UISwipeActionsConfiguration configurationWithActions:@[
[UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:@"Delete" handler:^(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed)) {
[self deleteItemAtIndexPath:indexPath completionHandler:completionHandler];
}]
]];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Expand Down Expand Up @@ -409,7 +406,6 @@ - (void)patchExecAndSignIfNeed:(NSIndexPath *)indexPath shouldSort:(BOOL)sortNam
[info removeObjectForKey:@"LCBundleIdentifier"];

__block NSProgress *progress = [LCUtils signAppBundle:appPathURL

completionHandler:^(BOOL success, NSError *_Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
Expand Down
10 changes: 8 additions & 2 deletions LCTabBarController.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "LCAppListViewController.h"
#import "LCSettingsListController.h"
#import "LCTweakListViewController.h"
#import "LCTabBarController.h"

@implementation LCTabBarController
Expand All @@ -10,16 +11,21 @@ - (void)loadView {
LCAppListViewController* appTableVC = [LCAppListViewController new];
appTableVC.title = @"Apps";

LCTweakListViewController* tweakTableVC = [LCTweakListViewController new];
tweakTableVC.title = @"Tweaks";

LCSettingsListController* settingsListVC = [LCSettingsListController new];
settingsListVC.title = @"Settings";

UINavigationController* appNavigationController = [[UINavigationController alloc] initWithRootViewController:appTableVC];
UINavigationController* tweakNavigationController = [[UINavigationController alloc] initWithRootViewController:tweakTableVC];
UINavigationController* settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsListVC];

appNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"square.stack.3d.up.fill"];
tweakNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"wrench.and.screwdriver"];
settingsNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"gear"];

self.viewControllers = @[appNavigationController, settingsNavigationController];
self.viewControllers = @[appNavigationController, tweakNavigationController, settingsNavigationController];
}

@end
255 changes: 255 additions & 0 deletions LCTweakListViewController (1).m
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
@import UniformTypeIdentifiers;

#import "LCTweakListViewController.h"
#import "UIKitPrivate.h"
#import "UIViewController+LCAlert.h"

@interface LCTweakListViewController()
@property(nonatomic) NSString *path;
@property(nonatomic) NSMutableArray *objects;
@end

@implementation LCTweakListViewController

- (void)loadView {
[super loadView];

if (!self.path) {
NSString *docPath = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject.path;
self.path = [docPath stringByAppendingPathComponent:@"Tweaks"];
}
[self loadPath];

UIMenu *addMenu = [UIMenu menuWithTitle:@"" image:nil identifier:nil
options:UIMenuOptionsDisplayInline
children:@[
[UIAction
actionWithTitle:@"Tweak"
image:[UIImage systemImageNamed:@"doc"]
identifier:nil handler:^(UIAction *action) {
[self addDylibButtonTapped];
}],
[UIAction
actionWithTitle:@"Folder"
image:[UIImage systemImageNamed:@"folder"]
identifier:nil handler:^(UIAction *action) {
[self addDirectoryButtonTapped];
}]
]];
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd menu:addMenu];
}

- (void)loadPath {
BOOL reload = self.objects != nil;

NSMutableArray *directories = [NSMutableArray new];
NSArray *files = [[NSFileManager.defaultManager contentsOfDirectoryAtPath:self.path error:nil] filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSString *name, NSDictionary *bindings) {
BOOL isDir;
NSString *path = [self.path stringByAppendingPathComponent:name];
[NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDir];
if (isDir) {
[directories addObject:name];
}
return !isDir && [name hasSuffix:@".dylib"];
return YES;
}]];

self.objects = [NSMutableArray new];
[self.objects addObjectsFromArray:[directories sortedArrayUsingSelector:@selector(compare:)]];
[self.objects addObjectsFromArray:[files sortedArrayUsingSelector:@selector(compare:)]];

if (reload) {
[self.tableView reloadData];
}
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}

/*
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
return @"N items";
}
*/

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.objects.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"];
}

UIListContentConfiguration *config = cell.defaultContentConfiguration;
config.text = self.objects[indexPath.row];

BOOL isDir;
NSString *path = [self.path stringByAppendingPathComponent:self.objects[indexPath.row]];
[NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDir];
config.image = [UIImage systemImageNamed:(isDir ? @"folder.fill" : @"doc")];

if (isDir) {
config.secondaryText = @"folder";
} else {
NSDictionary *attrs = [NSFileManager.defaultManager attributesOfItemAtPath:path error:nil];
NSNumber *size = attrs[NSFileSize];
config.secondaryText = [NSByteCountFormatter stringFromByteCount:size.unsignedLongLongValue
countStyle:NSByteCountFormatterCountStyleFile];
}

cell.contentConfiguration = config;
cell.selectionStyle = isDir ?
UITableViewCellSelectionStyleDefault :
UITableViewCellSelectionStyleNone;
return cell;
}

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.selectionStyle == UITableViewCellSelectionStyleNone) {
return nil;
} else {
return indexPath;
}
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
NSString *name = self.objects[indexPath.row];
LCTweakListViewController *childVC = [LCTweakListViewController new];
childVC.path = [self.path stringByAppendingPathComponent:name];
childVC.title = name;
[self.navigationController pushViewController:childVC animated:YES];
}

- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^)(BOOL actionPerformed))handler {
NSString *name = self.objects[indexPath.row];
NSString *path = [self.path stringByAppendingPathComponent:name];
[self showConfirmationDialogTitle:@"Confirm"
message:[NSString stringWithFormat:@"Are you sure you want to delete %@?", name]
confirmButtonTitle:@"Delete"
handler:^(UIAlertAction * action) {
if (action.style == UIAlertActionStyleCancel) return;
NSError *error = nil;
[NSFileManager.defaultManager removeItemAtPath:path error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
} else {
[self.objects removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
handler(YES);
}];
}

- (UISwipeActionsConfiguration *) tableView:(UITableView *)tableView
trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
return [UISwipeActionsConfiguration configurationWithActions:@[
[UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
title:@"Delete" handler:^(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed)) {
[self deleteItemAtIndexPath:indexPath completionHandler:completionHandler];
}]
]];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
NSProgress *progress = (NSProgress *)object;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress.fractionCompleted;
});
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
NSError *error;
NSString *path = self.path;
if (LCUtils.certificatePassword) {
// Move them to a tmp folder to sign them
path = [self.path stringByAppendingPathComponent:@".tmp"];
[NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:@{} error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
return;
}
}

for (NSURL *url in urls) {
NSString *filePath = [path stringByAppendingPathComponent:url.path.lastPathComponent];
[NSFileManager.defaultManager moveItemAtPath:url.path toPath:filePath error:&error];
if (error) {
[self showDialogTitle:@"Error" message:error.localizedDescription];
return;
}
}

if (!LCUtils.certificatePassword) {
// JIT stop here
return;
}

// Setup a fake app bundle for signing
NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"];
NSString *tmpInfoPath = [path stringByAppendingPathComponent:@"Info.plist"];
[NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil];
NSMutableDictionary *info = NSBundle.mainBundle.infoDictionary.mutableCopy;
info[@"CFBundleExecutable"] = @"LiveContainer.tmp";
[info writeToFile:tmpInfoPath atomically:YES];

dispatch_block_t handler = ^{
if (error) {
[self showDialogTitle:@"Error while signing tweaks" message:error.localizedDescription];
} else {
// Move tweaks back
for (NSURL *url in urls) {
NSString *fromPath = [path stringByAppendingPathComponent:url.path.lastPathComponent];
NSString *toPath = [self.path stringByAppendingPathComponent:url.path.lastPathComponent];
[NSFileManager.defaultManager moveItemAtPath:fromPath toPath:toPath error:&error];
}
}

// Remove tmp folder
[NSFileManager.defaultManager removeItemAtPath:path error:nil];
[progress removeObserver:self forKeyPath:@"fractionCompleted"];
[self.progressView removeFromSuperview];
[self loadPath];
};

NSProgress *progress = [LCUtils signAppBundle:appPathURL
completionHandler:^(BOOL success, NSError *_Nullable signError) {
error = signError;
dispatch_async(dispatch_get_main_queue(), handler);
}];

if (progress) {
[progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil];
}
}

- (void)addDirectoryButtonTapped {
[self showInputDialogTitle:@"Add folder" message:@"Enter name" placeholder:@"Name" callback:^(NSString *name) {
NSError *error;
NSString *path = [self.path stringByAppendingPathComponent:name];
[NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:@{} error:&error];
[self loadPath];
return error.localizedDescription;
}];
}

- (void)addDylibButtonTapped {
UIDocumentPickerViewController *documentPickerVC = [[UIDocumentPickerViewController alloc]
initForOpeningContentTypes:@[[UTType typeWithFilenameExtension:@"dylib" conformingToType:UTTypeData]]
asCopy:YES];
documentPickerVC.allowsMultipleSelection = YES;
documentPickerVC.delegate = self;
[self presentViewController:documentPickerVC animated:YES completion:nil];
}

@end
4 changes: 4 additions & 0 deletions LCTweakListViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@import UIKit;

@interface LCTweakListViewController : UITableViewController <UIDocumentPickerDelegate>
@end
Loading

0 comments on commit 191d9ab

Please sign in to comment.