TrollStore 1.2, new build setup, deprecate installers in favor of TrollHelperOTA, add jailbreak guide, add better version compatibility notes
@@ -0,0 +1,14 @@
|
||||
TARGET := iphone:clang:14.5:14.0
|
||||
INSTALL_TARGET_PROCESSES = TrollStore
|
||||
|
||||
include $(THEOS)/makefiles/common.mk
|
||||
|
||||
APPLICATION_NAME = TrollStore
|
||||
|
||||
TrollStore_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m)
|
||||
TrollStore_FRAMEWORKS = UIKit CoreGraphics CoreServices
|
||||
TrollStore_PRIVATE_FRAMEWORKS = Preferences
|
||||
TrollStore_CFLAGS = -fobjc-arc -I../Shared
|
||||
TrollStore_CODESIGN_FLAGS = -Sentitlements.plist -K../cert.p12
|
||||
|
||||
include $(THEOS_MAKE_PATH)/application.mk
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,259 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>TrollStore</string>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon29x29</string>
|
||||
<string>AppIcon40x40</string>
|
||||
<string>AppIcon57x57</string>
|
||||
<string>AppIcon60x60</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict>
|
||||
<key>CFBundlePrimaryIcon</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>AppIcon29x29</string>
|
||||
<string>AppIcon40x40</string>
|
||||
<string>AppIcon57x57</string>
|
||||
<string>AppIcon60x60</string>
|
||||
<string>AppIcon50x50</string>
|
||||
<string>AppIcon72x72</string>
|
||||
<string>AppIcon76x76</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.opa334.TrollStore</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.2</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UILaunchImageFile</key>
|
||||
<string>LaunchImage</string>
|
||||
<key>UILaunchImages</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Portrait</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{320, 480}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-700-568h</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Portrait</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{320, 568}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-Portrait</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Portrait</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{768, 1024}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>7.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-Landscape</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Landscape</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{768, 1024}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-800-667h</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Portrait</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{375, 667}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-800-Portrait-736h</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Portrait</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{414, 736}</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UILaunchImageMinimumOSVersion</key>
|
||||
<string>8.0</string>
|
||||
<key>UILaunchImageName</key>
|
||||
<string>LaunchImage-800-Landscape-736h</string>
|
||||
<key>UILaunchImageOrientation</key>
|
||||
<string>Landscape</string>
|
||||
<key>UILaunchImageSize</key>
|
||||
<string>{414, 736}</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>TSSceneDelegate</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>iOS App</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.apple.itunes.ipa</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>ipa</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>iOS App</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Default</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.apple.itunes.ipa</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>TrollStore Update</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Default</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.tar-archive</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>AirDrop friendy iOS app</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.opa334.trollstore.tipa</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.opa334.trollstore.tipa</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AirDrop friendy iOS app</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>tipa</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<string>application/trollstore-ipa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>TSRootBinaries</key>
|
||||
<array>
|
||||
<string>trollstorehelper</string>
|
||||
<string>ldid</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>TROLLTROLL.*</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>TROLLTROLL</string>
|
||||
<key>get-task-allow</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>TROLLTROLL.*</string>
|
||||
<string>com.apple.token</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TSAppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
#import "TSAppDelegate.h"
|
||||
#import "TSRootViewController.h"
|
||||
|
||||
@implementation TSAppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
|
||||
}
|
||||
|
||||
|
||||
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TSAppTableViewController : UITableViewController
|
||||
{
|
||||
UIImage* _placeholderIcon;
|
||||
NSArray* _cachedAppPaths;
|
||||
NSMutableDictionary* _cachedIcons;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,277 @@
|
||||
#import "TSAppTableViewController.h"
|
||||
|
||||
#import "TSApplicationsManager.h"
|
||||
|
||||
#define ICON_FORMAT_IPAD 8
|
||||
#define ICON_FORMAT_IPHONE 10
|
||||
|
||||
NSInteger iconFormatToUse(void)
|
||||
{
|
||||
if(UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
||||
{
|
||||
return ICON_FORMAT_IPAD;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ICON_FORMAT_IPHONE;
|
||||
}
|
||||
}
|
||||
|
||||
UIImage* imageWithSize(UIImage* image, CGSize size)
|
||||
{
|
||||
if(CGSizeEqualToSize(image.size, size)) return image;
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
|
||||
CGRect imageRect = CGRectMake(0.0, 0.0, size.width, size.height);
|
||||
[image drawInRect:imageRect];
|
||||
UIImage* outImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return outImage;
|
||||
}
|
||||
|
||||
@interface UIImage ()
|
||||
+ (UIImage *)_applicationIconImageForBundleIdentifier:(NSString *)id format:(NSInteger)format scale:(double)scale;
|
||||
@end
|
||||
|
||||
@implementation TSAppTableViewController
|
||||
|
||||
- (void)loadCachedAppPaths
|
||||
{
|
||||
NSArray* appPaths = [[TSApplicationsManager sharedInstance] installedAppPaths];
|
||||
|
||||
_cachedAppPaths = [appPaths sortedArrayUsingComparator:^NSComparisonResult(NSString* appPathA, NSString* appPathB) {
|
||||
NSString* displayNameA = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPathA];
|
||||
NSString* displayNameB = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPathB];
|
||||
|
||||
return [displayNameA localizedStandardCompare:displayNameB];
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
[self loadCachedAppPaths];
|
||||
_placeholderIcon = [UIImage _applicationIconImageForBundleIdentifier:@"com.apple.WebSheet" format:iconFormatToUse() scale:[UIScreen mainScreen].scale];
|
||||
_cachedIcons = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadTable
|
||||
{
|
||||
[self loadCachedAppPaths];
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self.tableView reloadData];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reloadTable)
|
||||
name:@"ApplicationsChanged"
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.tableView.allowsMultipleSelectionDuringEditing = NO;
|
||||
}
|
||||
|
||||
- (void)showError:(NSError*)error
|
||||
{
|
||||
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
|
||||
[errorAlert addAction:closeAction];
|
||||
[self presentViewController:errorAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)openAppPressedForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance];
|
||||
|
||||
NSString* appPath = _cachedAppPaths[indexPath.row];
|
||||
NSString* appId = [appsManager appIdForAppPath:appPath];
|
||||
BOOL didOpen = [appsManager openApplicationWithBundleID:appId];
|
||||
|
||||
// if we failed to open the app, show an alert
|
||||
if (!didOpen) {
|
||||
NSString *failMessage = [NSString stringWithFormat: @"Failed to open %@", appId];
|
||||
UIAlertController* didFailController = [UIAlertController alertControllerWithTitle:failMessage message: nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
|
||||
|
||||
[didFailController addAction: cancelAction];
|
||||
[self presentViewController:didFailController animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)uninstallPressedForRowAtIndexPath:(NSIndexPath*)indexPath
|
||||
{
|
||||
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance];
|
||||
|
||||
NSString* appPath = _cachedAppPaths[indexPath.row];
|
||||
NSString* appId = [appsManager appIdForAppPath:appPath];
|
||||
NSString* appName = [appsManager displayNameForAppPath:appPath];
|
||||
|
||||
UIAlertController* confirmAlert = [UIAlertController alertControllerWithTitle:@"Confirm Uninstallation" message:[NSString stringWithFormat:@"Uninstalling the app '%@' will delete the app and all data associated to it.", appName] preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action)
|
||||
{
|
||||
if(appId)
|
||||
{
|
||||
[appsManager uninstallApp:appId];
|
||||
}
|
||||
else
|
||||
{
|
||||
[appsManager uninstallAppByPath:appPath];
|
||||
}
|
||||
}];
|
||||
[confirmAlert addAction:uninstallAction];
|
||||
|
||||
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
|
||||
[confirmAlert addAction:cancelAction];
|
||||
|
||||
[self presentViewController:confirmAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)deselectRow
|
||||
{
|
||||
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Table view data source
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return _cachedAppPaths.count;
|
||||
}
|
||||
|
||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
||||
{
|
||||
[self reloadTable];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ApplicationCell"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ApplicationCell"];
|
||||
}
|
||||
|
||||
NSString* appPath = _cachedAppPaths[indexPath.row];
|
||||
NSString* appId = [[TSApplicationsManager sharedInstance] appIdForAppPath:appPath];
|
||||
NSString* appVersion = [[TSApplicationsManager sharedInstance] versionStringForAppPath:appPath];
|
||||
|
||||
// Configure the cell...
|
||||
cell.textLabel.text = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPath];
|
||||
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ • %@", appVersion, appId];
|
||||
cell.imageView.layer.borderWidth = 1;
|
||||
cell.imageView.layer.borderColor = [UIColor.labelColor colorWithAlphaComponent:0.1].CGColor;
|
||||
cell.imageView.layer.cornerRadius = 13.5;
|
||||
cell.imageView.layer.masksToBounds = YES;
|
||||
cell.imageView.layer.cornerCurve = kCACornerCurveContinuous;
|
||||
|
||||
if(appId)
|
||||
{
|
||||
UIImage* cachedIcon = _cachedIcons[appId];
|
||||
if(cachedIcon)
|
||||
{
|
||||
cell.imageView.image = cachedIcon;
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.image = _placeholderIcon;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
|
||||
{
|
||||
//usleep(1000 * 5000); // (test delay for debugging)
|
||||
UIImage* iconImage = imageWithSize([UIImage _applicationIconImageForBundleIdentifier:appId format:iconFormatToUse() scale:[UIScreen mainScreen].scale], _placeholderIcon.size);
|
||||
_cachedIcons[appId] = iconImage;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if([tableView.indexPathsForVisibleRows containsObject:indexPath])
|
||||
{
|
||||
cell.imageView.image = iconImage;
|
||||
[cell setNeedsLayout];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.imageView.image = _placeholderIcon;
|
||||
}
|
||||
|
||||
cell.preservesSuperviewLayoutMargins = NO;
|
||||
cell.separatorInset = UIEdgeInsetsZero;
|
||||
cell.layoutMargins = UIEdgeInsetsZero;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return 80.0f;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if(editingStyle == UITableViewCellEditingStyleDelete)
|
||||
{
|
||||
[self uninstallPressedForRowAtIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance];
|
||||
|
||||
NSString* appPath = _cachedAppPaths[indexPath.row];
|
||||
NSString* appId = [appsManager appIdForAppPath:appPath];
|
||||
NSString* appName = [appsManager displayNameForAppPath:appPath];
|
||||
|
||||
UIAlertController* appSelectAlert = [UIAlertController alertControllerWithTitle:appName message:appId?:@"" preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
/*UIAlertAction* detachAction = [UIAlertAction actionWithTitle:@"Detach from TrollStore" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
int detachRet = [appsManager detachFromApp:appId];
|
||||
if(detachRet != 0)
|
||||
{
|
||||
[self showError:[appsManager errorForCode:detachRet]];
|
||||
}
|
||||
[self deselectRow];
|
||||
}];
|
||||
[appSelectAlert addAction:detachAction];*/
|
||||
|
||||
|
||||
UIAlertAction* openAction = [UIAlertAction actionWithTitle:@"Open" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
[self openAppPressedForRowAtIndexPath:indexPath];
|
||||
[self deselectRow];
|
||||
}];
|
||||
[appSelectAlert addAction: openAction];
|
||||
|
||||
UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall App" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action)
|
||||
{
|
||||
[self uninstallPressedForRowAtIndexPath:indexPath];
|
||||
[self deselectRow];
|
||||
}];
|
||||
[appSelectAlert addAction:uninstallAction];
|
||||
|
||||
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action)
|
||||
{
|
||||
[self deselectRow];
|
||||
}];
|
||||
[appSelectAlert addAction:cancelAction];
|
||||
|
||||
appSelectAlert.popoverPresentationController.sourceView = tableView;
|
||||
appSelectAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
|
||||
|
||||
[self presentViewController:appSelectAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define TROLLSTORE_ROOT_PATH @"/var/containers/Bundle/TrollStore"
|
||||
#define TROLLSTORE_MAIN_PATH [TROLLSTORE_ROOT_PATH stringByAppendingPathComponent:@"Main"]
|
||||
#define TROLLSTORE_APPLICATIONS_PATH [TROLLSTORE_ROOT_PATH stringByAppendingPathComponent:@"Applications"]
|
||||
|
||||
@interface TSApplicationsManager : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (NSArray*)installedAppPaths;
|
||||
- (NSDictionary*)infoDictionaryForAppPath:(NSString*)appPath;
|
||||
- (NSString*)appIdForAppPath:(NSString*)appPath;
|
||||
- (NSString*)displayNameForAppPath:(NSString*)appPath;
|
||||
- (NSString*)versionStringForAppPath:(NSString*)appPath;
|
||||
|
||||
- (NSError*)errorForCode:(int)code;
|
||||
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut;
|
||||
- (int)installIpa:(NSString*)pathToIpa;
|
||||
- (int)uninstallApp:(NSString*)appId;
|
||||
- (int)uninstallAppByPath:(NSString*)path;
|
||||
- (BOOL)openApplicationWithBundleID:(NSString *)appID;
|
||||
//- (int)detachFromApp:(NSString*)appId;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,171 @@
|
||||
#import "TSApplicationsManager.h"
|
||||
#import <TSUtil.h>
|
||||
|
||||
#define TrollStoreErrorDomain @"TrollStoreErrorDomain"
|
||||
|
||||
@implementation TSApplicationsManager
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static TSApplicationsManager *sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[TSApplicationsManager alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (NSArray*)installedAppPaths
|
||||
{
|
||||
return trollStoreInstalledAppBundlePaths();
|
||||
}
|
||||
|
||||
- (NSDictionary*)infoDictionaryForAppPath:(NSString*)appPath
|
||||
{
|
||||
NSString* infoPlistPath = [appPath stringByAppendingPathComponent:@"Info.plist"];
|
||||
NSError* error;
|
||||
NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:infoPlistPath] error:&error];
|
||||
if(error)
|
||||
{
|
||||
NSLog(@"error getting info dict: %@", error);
|
||||
}
|
||||
return infoDict;
|
||||
}
|
||||
|
||||
- (NSString*)appIdForAppPath:(NSString*)appPath
|
||||
{
|
||||
return [self infoDictionaryForAppPath:appPath][@"CFBundleIdentifier"];
|
||||
}
|
||||
|
||||
- (NSString*)displayNameForAppPath:(NSString*)appPath
|
||||
{
|
||||
NSDictionary* infoDict = [self infoDictionaryForAppPath:appPath];
|
||||
NSString* displayName = infoDict[@"CFBundleDisplayName"];
|
||||
if(![displayName isKindOfClass:[NSString class]]) displayName = nil;
|
||||
if(!displayName || [displayName isEqualToString:@""])
|
||||
{
|
||||
displayName = infoDict[@"CFBundleName"];
|
||||
if(![displayName isKindOfClass:[NSString class]]) displayName = nil;
|
||||
if(!displayName || [displayName isEqualToString:@""])
|
||||
{
|
||||
displayName = infoDict[@"CFBundleExecutable"];
|
||||
if(![displayName isKindOfClass:[NSString class]]) displayName = [appPath lastPathComponent];
|
||||
}
|
||||
}
|
||||
|
||||
return displayName;
|
||||
}
|
||||
|
||||
- (NSString*)versionStringForAppPath:(NSString*)appPath
|
||||
{
|
||||
NSDictionary* infoDict = [self infoDictionaryForAppPath:appPath];
|
||||
NSString* versionString = infoDict[@"CFBundleShortVersionString"];
|
||||
|
||||
if(!versionString)
|
||||
{
|
||||
versionString = infoDict[@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
return versionString;
|
||||
}
|
||||
|
||||
- (NSError*)errorForCode:(int)code
|
||||
{
|
||||
NSString* errorDescription = @"Unknown Error";
|
||||
switch(code)
|
||||
{
|
||||
// IPA install errors
|
||||
case 166:
|
||||
errorDescription = @"The IPA file does not exist or is not accessible.";
|
||||
break;
|
||||
case 167:
|
||||
errorDescription = @"The IPA file does not appear to contain an app.";
|
||||
break;
|
||||
// App install errors
|
||||
case 170:
|
||||
errorDescription = @"Failed to create container for app bundle.";
|
||||
break;
|
||||
case 171:
|
||||
errorDescription = @"A non-TrollStore app with the same identifier is already installed. If you are absolutely sure it is not, you can force install it.";
|
||||
break;
|
||||
case 172:
|
||||
errorDescription = @"The app does not contain an Info.plist file.";
|
||||
break;
|
||||
case 173:
|
||||
errorDescription = @"The app is not signed with a fake CoreTrust certificate and ldid is not installed. Install ldid in the settings tab and try again.";
|
||||
break;
|
||||
case 174:
|
||||
errorDescription = @"The apps main executable does not exists.";
|
||||
break;
|
||||
case 175:
|
||||
errorDescription = @"Failed to sign the app. ldid returned a non zero status code.";
|
||||
break;
|
||||
case 176:
|
||||
errorDescription = @"The apps Info.plist is missing required values.";
|
||||
break;
|
||||
case 177:
|
||||
errorDescription = @"Failed to mark app as TrollStore app.";
|
||||
break;
|
||||
case 178:
|
||||
errorDescription = @"Failed to copy app bundle.";
|
||||
break;
|
||||
// App detach errors
|
||||
/*case 184:
|
||||
errorDescription = @"Refusing to detach, the app is still signed with a fake root certificate. The detach option is only for when you have installed an App Store app on top of a TrollStore app.";
|
||||
break;*/
|
||||
}
|
||||
|
||||
NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}];
|
||||
return error;
|
||||
}
|
||||
|
||||
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut
|
||||
{
|
||||
int ret;
|
||||
if(force)
|
||||
{
|
||||
ret = spawnRoot(rootHelperPath(), @[@"install", pathToIpa, @"force"], nil, logOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = spawnRoot(rootHelperPath(), @[@"install", pathToIpa], nil, logOut);
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil];
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (int)installIpa:(NSString*)pathToIpa
|
||||
{
|
||||
return [self installIpa:pathToIpa force:NO log:nil];
|
||||
}
|
||||
|
||||
- (int)uninstallApp:(NSString*)appId
|
||||
{
|
||||
if(!appId) return -200;
|
||||
int ret = spawnRoot(rootHelperPath(), @[@"uninstall", appId], nil, nil);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil];
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (int)uninstallAppByPath:(NSString*)path
|
||||
{
|
||||
if(!path) return -200;
|
||||
int ret = spawnRoot(rootHelperPath(), @[@"uninstall-path", path], nil, nil);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil];
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (BOOL)openApplicationWithBundleID:(NSString *)appId
|
||||
{
|
||||
return [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:appId];
|
||||
}
|
||||
|
||||
/*- (int)detachFromApp:(NSString*)appId
|
||||
{
|
||||
if(!appId) return -200;
|
||||
int ret = spawnRoot(rootHelperPath(), @[@"detach", appId], nil, nil);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil];
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,5 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TSRootViewController : UITabBarController
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
#import "TSRootViewController.h"
|
||||
#import "TSAppTableViewController.h"
|
||||
#import "TSSettingsListController.h"
|
||||
|
||||
@implementation TSRootViewController
|
||||
|
||||
- (void)loadView {
|
||||
[super loadView];
|
||||
|
||||
TSAppTableViewController* appTableVC = [[TSAppTableViewController alloc] init];
|
||||
appTableVC.title = @"Apps";
|
||||
|
||||
TSSettingsListController* settingsListVC = [[TSSettingsListController alloc] init];
|
||||
settingsListVC.title = @"Settings";
|
||||
|
||||
UINavigationController* appNavigationController = [[UINavigationController alloc] initWithRootViewController:appTableVC];
|
||||
UINavigationController* settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsListVC];
|
||||
|
||||
appNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"square.stack.3d.up.fill"];
|
||||
settingsNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"gear"];
|
||||
|
||||
self.title = @"Root View Controller";
|
||||
self.viewControllers = @[appNavigationController, settingsNavigationController];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,6 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TSSceneDelegate : UIResponder <UIWindowSceneDelegate>
|
||||
@property (strong, nonatomic) UIWindow * window;
|
||||
@property (nonatomic, strong) UITabBarController *rootViewController;
|
||||
@end
|
||||
@@ -0,0 +1,186 @@
|
||||
#import "TSSceneDelegate.h"
|
||||
#import "TSRootViewController.h"
|
||||
#import "TSUtil.h"
|
||||
#import "TSApplicationsManager.h"
|
||||
|
||||
@implementation TSSceneDelegate
|
||||
|
||||
- (void)doIPAInstall:(NSString*)ipaPath scene:(UIWindowScene*)scene force:(BOOL)force completion:(void (^)(void))completion
|
||||
{
|
||||
UIWindow* keyWindow = nil;
|
||||
for(UIWindow* window in scene.windows)
|
||||
{
|
||||
if(window.isKeyWindow)
|
||||
{
|
||||
keyWindow = window;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UIAlertController* infoAlert = [UIAlertController alertControllerWithTitle:@"Installing" message:@"" preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(5,5,50,50)];
|
||||
activityIndicator.hidesWhenStopped = YES;
|
||||
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
|
||||
[activityIndicator startAnimating];
|
||||
[infoAlert.view addSubview:activityIndicator];
|
||||
|
||||
[keyWindow.rootViewController presentViewController:infoAlert animated:YES completion:nil];
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
|
||||
{
|
||||
// Install IPA
|
||||
//NSString* log;
|
||||
int ret = [[TSApplicationsManager sharedInstance] installIpa:ipaPath force:force log:nil];
|
||||
|
||||
NSError* error;
|
||||
if(ret != 0)
|
||||
{
|
||||
error = [[TSApplicationsManager sharedInstance] errorForCode:ret];
|
||||
}
|
||||
|
||||
NSLog(@"installed app! ret:%d, error: %@", ret, error);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[infoAlert dismissViewControllerAnimated:YES completion:^
|
||||
{
|
||||
if(ret != 0)
|
||||
{
|
||||
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Install Error %d", ret] message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
if(ret == 171)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
[errorAlert addAction:closeAction];
|
||||
|
||||
if(ret == 171)
|
||||
{
|
||||
UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
[self doIPAInstall:ipaPath scene:scene force:YES completion:completion];
|
||||
}];
|
||||
[errorAlert addAction:forceInstallAction];
|
||||
}
|
||||
else
|
||||
{
|
||||
/*UIAlertAction* copyLogAction = [UIAlertAction actionWithTitle:@"Copy Log" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
|
||||
pasteboard.string = log;
|
||||
}];
|
||||
[errorAlert addAction:copyLogAction];*/
|
||||
}
|
||||
|
||||
[keyWindow.rootViewController presentViewController:errorAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
if(ret != 171)
|
||||
{
|
||||
completion();
|
||||
}
|
||||
}];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)handleURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts scene:(UIWindowScene*)scene
|
||||
{
|
||||
for(UIOpenURLContext* context in URLContexts)
|
||||
{
|
||||
NSLog(@"openURLContexts %@", context.URL);
|
||||
NSURL* url = context.URL;
|
||||
if (url != nil && [url isFileURL]) {
|
||||
[url startAccessingSecurityScopedResource];
|
||||
void (^doneBlock)(BOOL) = ^(BOOL shouldExit)
|
||||
{
|
||||
[url stopAccessingSecurityScopedResource];
|
||||
[[NSFileManager defaultManager] removeItemAtURL:url error:nil];
|
||||
|
||||
if(shouldExit)
|
||||
{
|
||||
NSLog(@"Respring + Exit");
|
||||
respring();
|
||||
exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
if ([url.pathExtension.lowercaseString isEqualToString:@"ipa"] || [url.pathExtension.lowercaseString isEqualToString:@"tipa"])
|
||||
{
|
||||
[self doIPAInstall:url.path scene:(UIWindowScene*)scene force:NO completion:^{
|
||||
doneBlock(NO);
|
||||
}];
|
||||
}
|
||||
else if([url.pathExtension.lowercaseString isEqualToString:@"tar"])
|
||||
{
|
||||
// Update TrollStore itself
|
||||
NSLog(@"Updating TrollStore...");
|
||||
int ret = spawnRoot(rootHelperPath(), @[@"install-trollstore", url.path], nil, nil);
|
||||
doneBlock(ret == 0);
|
||||
NSLog(@"Updated TrollStore!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
|
||||
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
|
||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||
|
||||
NSLog(@"scene:%@ willConnectToSession:%@ options:%@", scene, session, connectionOptions);
|
||||
UIWindowScene* windowScene = (UIWindowScene*)scene;
|
||||
_window = [[UIWindow alloc] initWithWindowScene:windowScene];
|
||||
_rootViewController = [[TSRootViewController alloc] init];
|
||||
_window.rootViewController = _rootViewController;
|
||||
[_window makeKeyAndVisible];
|
||||
|
||||
if(connectionOptions.URLContexts.count)
|
||||
{
|
||||
[self handleURLContexts:connectionOptions.URLContexts scene:(UIWindowScene*)scene];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidDisconnect:(UIScene *)scene {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidBecomeActive:(UIScene *)scene {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneWillResignActive:(UIScene *)scene {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneWillEnterForeground:(UIScene *)scene {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
|
||||
- (void)sceneDidEnterBackground:(UIScene *)scene {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts
|
||||
{
|
||||
NSLog(@"scene:%@ openURLContexts:%@", scene, URLContexts);
|
||||
[self handleURLContexts:URLContexts scene:(UIWindowScene*)scene];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,8 @@
|
||||
#import "TSListControllerShared.h"
|
||||
|
||||
@interface TSSettingsListController : TSListControllerShared
|
||||
{
|
||||
PSSpecifier* _installPersistenceHelperSpecifier;
|
||||
NSString* _newerVersion;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,320 @@
|
||||
#import "TSSettingsListController.h"
|
||||
#import <TSUtil.h>
|
||||
#import <Preferences/PSSpecifier.h>
|
||||
|
||||
@implementation TSSettingsListController
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
|
||||
fetchLatestTrollStoreVersion(^(NSString* latestVersion)
|
||||
{
|
||||
NSString* currentVersion = [self getTrollStoreVersion];
|
||||
NSComparisonResult result = [currentVersion compare:latestVersion options:NSNumericSearch];
|
||||
if(result == NSOrderedAscending)
|
||||
{
|
||||
_newerVersion = latestVersion;
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self reloadSpecifiers];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (NSMutableArray*)specifiers
|
||||
{
|
||||
if(!_specifiers)
|
||||
{
|
||||
_specifiers = [NSMutableArray new];
|
||||
|
||||
if(_newerVersion)
|
||||
{
|
||||
PSSpecifier* updateTrollStoreGroupSpecifier = [PSSpecifier emptyGroupSpecifier];
|
||||
updateTrollStoreGroupSpecifier.name = @"Update Available";
|
||||
[_specifiers addObject:updateTrollStoreGroupSpecifier];
|
||||
|
||||
PSSpecifier* updateTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Update TrollStore to %@", _newerVersion]
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
updateTrollStoreSpecifier.identifier = @"updateTrollStore";
|
||||
[updateTrollStoreSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
updateTrollStoreSpecifier.buttonAction = @selector(updateTrollStorePressed);
|
||||
[_specifiers addObject:updateTrollStoreSpecifier];
|
||||
}
|
||||
|
||||
PSSpecifier* utilitiesGroupSpecifier = [PSSpecifier emptyGroupSpecifier];
|
||||
utilitiesGroupSpecifier.name = @"Utilities";
|
||||
[utilitiesGroupSpecifier setProperty:@"If an app does not immediately appear after installation, respring here and it should appear afterwards." forKey:@"footerText"];
|
||||
[_specifiers addObject:utilitiesGroupSpecifier];
|
||||
|
||||
PSSpecifier* respringButtonSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Respring"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
respringButtonSpecifier.identifier = @"respring";
|
||||
[respringButtonSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
respringButtonSpecifier.buttonAction = @selector(respringButtonPressed);
|
||||
|
||||
[_specifiers addObject:respringButtonSpecifier];
|
||||
|
||||
PSSpecifier* rebuildIconCacheSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Rebuild Icon Cache"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
rebuildIconCacheSpecifier.identifier = @"uicache";
|
||||
[rebuildIconCacheSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
rebuildIconCacheSpecifier.buttonAction = @selector(rebuildIconCachePressed);
|
||||
|
||||
[_specifiers addObject:rebuildIconCacheSpecifier];
|
||||
|
||||
NSString* ldidPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"ldid"];
|
||||
BOOL ldidInstalled = [[NSFileManager defaultManager] fileExistsAtPath:ldidPath];
|
||||
|
||||
PSSpecifier* signingGroupSpecifier = [PSSpecifier emptyGroupSpecifier];
|
||||
signingGroupSpecifier.name = @"Signing";
|
||||
|
||||
if(ldidInstalled)
|
||||
{
|
||||
[signingGroupSpecifier setProperty:@"ldid is installed and allows TrollStore to install unsigned IPA files." forKey:@"footerText"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[signingGroupSpecifier setProperty:@"In order for TrollStore to be able to install unsigned IPAs, ldid has to be installed using this button. It can't be directly included in TrollStore because of licensing issues." forKey:@"footerText"];
|
||||
}
|
||||
|
||||
[_specifiers addObject:signingGroupSpecifier];
|
||||
|
||||
if(ldidInstalled)
|
||||
{
|
||||
PSSpecifier* ldidInstalledSpecifier = [PSSpecifier preferenceSpecifierNamed:@"ldid: Installed"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSStaticTextCell
|
||||
edit:nil];
|
||||
[ldidInstalledSpecifier setProperty:@NO forKey:@"enabled"];
|
||||
ldidInstalledSpecifier.identifier = @"ldidInstalled";
|
||||
[_specifiers addObject:ldidInstalledSpecifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
PSSpecifier* installLdidSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Install ldid"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
installLdidSpecifier.identifier = @"ldidInstalled";
|
||||
[installLdidSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
installLdidSpecifier.buttonAction = @selector(installLdidPressed);
|
||||
[_specifiers addObject:installLdidSpecifier];
|
||||
}
|
||||
|
||||
PSSpecifier* persistenceGroupSpecifier = [PSSpecifier emptyGroupSpecifier];
|
||||
persistenceGroupSpecifier.name = @"Persistence";
|
||||
[_specifiers addObject:persistenceGroupSpecifier];
|
||||
|
||||
if([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/TrollStorePersistenceHelper.app"])
|
||||
{
|
||||
[persistenceGroupSpecifier setProperty:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. If that happens, you can use the TrollHelper app on the home screen to refresh the app registrations, which will make them work again." forKey:@"footerText"];
|
||||
PSSpecifier* installedPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Helper Installed as Standalone App"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSStaticTextCell
|
||||
edit:nil];
|
||||
[installedPersistenceHelperSpecifier setProperty:@NO forKey:@"enabled"];
|
||||
installedPersistenceHelperSpecifier.identifier = @"persistenceHelperInstalled";
|
||||
[_specifiers addObject:installedPersistenceHelperSpecifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
LSApplicationProxy* persistenceApp = findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE_ALL);
|
||||
if(persistenceApp)
|
||||
{
|
||||
NSString* appName = [persistenceApp localizedName];
|
||||
|
||||
[persistenceGroupSpecifier setProperty:[NSString stringWithFormat:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. If that happens, you can use the persistence helper installed into %@ to refresh the app registrations, which will make them work again.", appName] forKey:@"footerText"];
|
||||
PSSpecifier* installedPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:[NSString stringWithFormat:@"Helper Installed into %@", appName]
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSStaticTextCell
|
||||
edit:nil];
|
||||
[installedPersistenceHelperSpecifier setProperty:@NO forKey:@"enabled"];
|
||||
installedPersistenceHelperSpecifier.identifier = @"persistenceHelperInstalled";
|
||||
[_specifiers addObject:installedPersistenceHelperSpecifier];
|
||||
|
||||
PSSpecifier* uninstallPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall Persistence Helper"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
|
||||
uninstallPersistenceHelperSpecifier.identifier = @"uninstallPersistenceHelper";
|
||||
[uninstallPersistenceHelperSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
[uninstallPersistenceHelperSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"];
|
||||
uninstallPersistenceHelperSpecifier.buttonAction = @selector(uninstallPersistenceHelperPressed);
|
||||
[_specifiers addObject:uninstallPersistenceHelperSpecifier];
|
||||
}
|
||||
else
|
||||
{
|
||||
[persistenceGroupSpecifier setProperty:@"When iOS rebuilds the icon cache, all TrollStore apps including TrollStore itself will be reverted to \"User\" state and either disappear or no longer launch. The only way to have persistence in a rootless environment is to replace a system application, here you can select a system app to replace with a persistence helper that can be used to refresh the registrations of all TrollStore related apps in case they disappear or no longer launch." forKey:@"footerText"];
|
||||
|
||||
_installPersistenceHelperSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Install Persistence Helper"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
_installPersistenceHelperSpecifier.identifier = @"installPersistenceHelper";
|
||||
[_installPersistenceHelperSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
_installPersistenceHelperSpecifier.buttonAction = @selector(installPersistenceHelperPressed);
|
||||
[_specifiers addObject:_installPersistenceHelperSpecifier];
|
||||
}
|
||||
}
|
||||
|
||||
PSSpecifier* otherGroupSpecifier = [PSSpecifier emptyGroupSpecifier];
|
||||
[otherGroupSpecifier setProperty:[NSString stringWithFormat:@"TrollStore %@\n\n© 2022 Lars Fröder (opa334)\n\nCredits:\n@LinusHenze: CoreTrust bug\n@zhuowei: CoreTrust bug writeup and cert\n@lunotech11, @SerenaKit, @tylinux: Various contributions\n@ProcursusTeam: uicache and ldid build\n@cstar_ow: uicache\n@saurik: ldid", [self getTrollStoreVersion]] forKey:@"footerText"];
|
||||
[_specifiers addObject:otherGroupSpecifier];
|
||||
|
||||
// Uninstall TrollStore
|
||||
PSSpecifier* uninstallTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall TrollStore"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
uninstallTrollStoreSpecifier.identifier = @"uninstallTrollStore";
|
||||
[uninstallTrollStoreSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
[uninstallTrollStoreSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"];
|
||||
uninstallTrollStoreSpecifier.buttonAction = @selector(uninstallTrollStorePressed);
|
||||
[_specifiers addObject:uninstallTrollStoreSpecifier];
|
||||
|
||||
/*PSSpecifier* doTheDashSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Do the Dash"
|
||||
target:self
|
||||
set:nil
|
||||
get:nil
|
||||
detail:nil
|
||||
cell:PSButtonCell
|
||||
edit:nil];
|
||||
doTheDashSpecifier.identifier = @"doTheDash";
|
||||
[doTheDashSpecifier setProperty:@YES forKey:@"enabled"];
|
||||
uninstallTrollStoreSpecifier.buttonAction = @selector(doTheDashPressed);
|
||||
[_specifiers addObject:doTheDashSpecifier];*/
|
||||
}
|
||||
|
||||
[(UINavigationItem *)self.navigationItem setTitle:@"Settings"];
|
||||
return _specifiers;
|
||||
}
|
||||
|
||||
- (void)respringButtonPressed
|
||||
{
|
||||
respring();
|
||||
}
|
||||
|
||||
- (void)installLdidPressed
|
||||
{
|
||||
NSURL* ldidURL = [NSURL URLWithString:@"https://github.com/opa334/ldid/releases/download/v2.1.5-procursus5/ldid"];
|
||||
NSURLRequest* ldidRequest = [NSURLRequest requestWithURL:ldidURL];
|
||||
|
||||
[self startActivity:@"Installing ldid"];
|
||||
|
||||
NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:ldidRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
|
||||
{
|
||||
if(error)
|
||||
{
|
||||
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error downloading ldid: %@", error] preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
|
||||
[errorAlert addAction:closeAction];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self stopActivityWithCompletion:^
|
||||
{
|
||||
[self presentViewController:errorAlert animated:YES completion:nil];
|
||||
}];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnRoot(rootHelperPath(), @[@"install-ldid", location.path], nil, nil);
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[self stopActivityWithCompletion:nil];
|
||||
[self reloadSpecifiers];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
[downloadTask resume];
|
||||
}
|
||||
|
||||
- (void)installPersistenceHelperPressed
|
||||
{
|
||||
NSMutableArray* appCandidates = [NSMutableArray new];
|
||||
[[LSApplicationWorkspace defaultWorkspace] enumerateApplicationsOfType:1 block:^(LSApplicationProxy* appProxy)
|
||||
{
|
||||
if(appProxy.installed && !appProxy.restricted)
|
||||
{
|
||||
if([appProxy.bundleURL.path hasPrefix:@"/private/var/containers"])
|
||||
{
|
||||
NSURL* trollStoreMarkURL = [appProxy.bundleURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"_TrollStore"];
|
||||
if(![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil])
|
||||
{
|
||||
[appCandidates addObject:appProxy];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
UIAlertController* selectAppAlert = [UIAlertController alertControllerWithTitle:@"Select App" message:@"Select a system app to install the TrollStore Persistence Helper into. The normal function of the app will not be available, so it is recommended to pick something useless like the Tips app." preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
for(LSApplicationProxy* appProxy in appCandidates)
|
||||
{
|
||||
UIAlertAction* installAction = [UIAlertAction actionWithTitle:[appProxy localizedName] style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
|
||||
{
|
||||
spawnRoot(rootHelperPath(), @[@"install-persistence-helper", appProxy.bundleIdentifier], nil, nil);
|
||||
[self reloadSpecifiers];
|
||||
}];
|
||||
|
||||
[selectAppAlert addAction:installAction];
|
||||
}
|
||||
|
||||
NSIndexPath* indexPath = [self indexPathForSpecifier:_installPersistenceHelperSpecifier];
|
||||
UITableView* tableView = [self valueForKey:@"_table"];
|
||||
selectAppAlert.popoverPresentationController.sourceView = tableView;
|
||||
selectAppAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
|
||||
|
||||
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
|
||||
[selectAppAlert addAction:cancelAction];
|
||||
|
||||
[self presentViewController:selectAppAlert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)doTheDashPressed
|
||||
{
|
||||
spawnRoot(rootHelperPath(), @[@"dash"], nil, nil);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,8 @@
|
||||
Package: com.opa334.trollstore
|
||||
Name: TrollStore
|
||||
Version: 1.2
|
||||
Architecture: iphoneos-arm
|
||||
Description: An awesome application!
|
||||
Maintainer: opa334
|
||||
Author: opa334
|
||||
Section: Utilities
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>com.opa334.TrollStore</string>
|
||||
<key>platform-application</key>
|
||||
<true/>
|
||||
<key>com.apple.security.exception.files.absolute-path.read-write</key>
|
||||
<array>
|
||||
<string>/</string>
|
||||
</array>
|
||||
<key>com.apple.private.security.no-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.private.persona-mgmt</key>
|
||||
<true/>
|
||||
<key>com.apple.private.security.container-manager</key>
|
||||
<true/>
|
||||
<key>com.apple.private.coreservices.canmaplsdatabase</key>
|
||||
<true/>
|
||||
<key>com.apple.lsapplicationworkspace.rebuildappdatabases</key>
|
||||
<true/>
|
||||
<key>com.apple.private.MobileContainerManager.allowed</key>
|
||||
<true/>
|
||||
<key>com.apple.private.MobileInstallationHelperService.InstallDaemonOpsEnabled</key>
|
||||
<true/>
|
||||
<key>com.apple.private.MobileInstallationHelperService.allowed</key>
|
||||
<true/>
|
||||
<key>com.apple.private.uninstall.deletion</key>
|
||||
<true/>
|
||||
<key>com.apple.private.security.storage.MobileDocuments</key>
|
||||
<true/>
|
||||
<key>com.apple.CommCenter.fine-grained</key>
|
||||
<array>
|
||||
<string>cellular-plan</string>
|
||||
<string>data-usage</string>
|
||||
<string>data-allowed-write</string>
|
||||
<string>preferences-write</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TSAppDelegate.h"
|
||||
#import "TSUtil.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@autoreleasepool {
|
||||
chineseWifiFixup();
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass(TSAppDelegate.class));
|
||||
}
|
||||
}
|
||||