This commit is contained in:
opa334 2022-10-30 00:45:30 +02:00
parent daacab0c8b
commit 029a80f4b9
44 changed files with 2019 additions and 484 deletions

View File

@ -38,7 +38,7 @@ This installation method unfortunately does **NOT** work on arm64 (A8 - A11) iOS
4. If this app has not appeared, that's a stock iOS bug, reboot your device and the app will appear. 4. If this app has not appeared, that's a stock iOS bug, reboot your device and the app will appear.
5. Launch the app, and tap "Install TrollStore" 5. Launch the app, tap "Install TrollStore"
6. Wait a few seconds, your device should respring and TrollStore will be installed. 6. Wait a few seconds, your device should respring and TrollStore will be installed.
@ -56,7 +56,7 @@ Supports jailbroken devices running 14.0 and above.
1. Open your package manager, and make sure Havoc repo (https://havoc.app) is added under Sources, then search for "TrollStore Helper" and install it. 1. Open your package manager, and make sure Havoc repo (https://havoc.app) is added under Sources, then search for "TrollStore Helper" and install it.
2. After the installation, respring and a "TrollHelper" app should be on your home screen, launch it. 2. After the installation, respring and the "TrollHelper" app should have appeared on your home screen.
3. Launch the app, tap "Install TrollStore" 3. Launch the app, tap "Install TrollStore"
@ -66,6 +66,18 @@ Supports jailbroken devices running 14.0 and above.
6. Done, you can now share IPA files with TrollStore and they will be permanently installed on your device. 6. Done, you can now share IPA files with TrollStore and they will be permanently installed on your device.
### Unjailbreaking while retaining TrollStore
Some people might prefer to use TrollStore in an unjailbroken environment, if that applies to you, follow this guide.
1. Uninstall TrollHelper from your package manager
2. Now when you launch TrollStore, it will have an option to install the persistence helper into a System app like on iOS 15, do so.
3. Now restore rootFS through your jailbreak app, afterwards use the System app to refresh app registrations.
4. Done, your device will be jailed, but TrollStore will still work.
## Updating TrollStore ## Updating TrollStore
When a new TrollStore update is available, a button to install it will appear at the top in the TrollStore settings. After tapping the button, TrollStore will automatically download the update, install it, and respring. When a new TrollStore update is available, a button to install it will appear at the top in the TrollStore settings. After tapping the button, TrollStore will automatically download the update, install it, and respring.
@ -84,6 +96,14 @@ The only way to work around this is to install a persistence helper into a syste
On jailbroken iOS 14 when TrollHelper is used for installation, it is located in /Applications and will persist as a "System" app through icon cache reloads, therefore TrollHelper is used as the persistence helper on iOS 14. On jailbroken iOS 14 when TrollHelper is used for installation, it is located in /Applications and will persist as a "System" app through icon cache reloads, therefore TrollHelper is used as the persistence helper on iOS 14.
## URL Scheme
As of version 1.3, TrollStore replaces the system URL scheme "apple-magnifier" (this is done so "jailbreak" detections can't detect TrollStore like they could if TrollStore had a unique URL scheme). This URL scheme can be used to install applications right from the browser, the format goes as follows:
`apple-magnifier://install?url=<URL_to_IPA>`
On devices that don't have TrollStore (1.3+) installed, this will just open the magnifier app.
## Features ## Features
The binaries inside an IPA can have arbitrary entitlements, fakesign them with ldid and the entitlements you want (`ldid -S<path/to/entitlements.plist> <path/to/binary>`) and TrollStore will preserve the entitlements when resigning them with the fake root certificate on installation. This gives you a lot of possibilities, some of which are explained below. The binaries inside an IPA can have arbitrary entitlements, fakesign them with ldid and the entitlements you want (`ldid -S<path/to/entitlements.plist> <path/to/binary>`) and TrollStore will preserve the entitlements when resigning them with the fake root certificate on installation. This gives you a lot of possibilities, some of which are explained below.

View File

@ -1,6 +1,6 @@
Package: com.opa334.trollstoreroothelper Package: com.opa334.trollstoreroothelper
Name: trollstoreroothelper Name: trollstoreroothelper
Version: 1.2.2 Version: 1.3
Architecture: iphoneos-arm Architecture: iphoneos-arm
Description: An awesome tool of some sort!! Description: An awesome tool of some sort!!
Maintainer: opa334 Maintainer: opa334

View File

@ -27,27 +27,6 @@ extern NSString* BKSOpenApplicationOptionKeyActivateForEvent;
extern void BKSTerminateApplicationForReasonAndReportWithDescription(NSString *bundleID, int reasonID, bool report, NSString *description); extern void BKSTerminateApplicationForReasonAndReportWithDescription(NSString *bundleID, int reasonID, bool report, NSString *description);
typedef CF_OPTIONS(uint32_t, SecCSFlags) {
kSecCSDefaultFlags = 0
};
#define kSecCSRequirementInformation 1 << 2
#define kSecCSSigningInformation 1 << 1
typedef struct __SecCode const *SecStaticCodeRef;
extern CFStringRef kSecCodeInfoEntitlementsDict;
extern CFStringRef kSecCodeInfoCertificates;
extern CFStringRef kSecPolicyAppleiPhoneApplicationSigning;
extern CFStringRef kSecPolicyAppleiPhoneProfileApplicationSigning;
extern CFStringRef kSecPolicyLeafMarkerOid;
OSStatus SecStaticCodeCreateWithPathAndAttributes(CFURLRef path, SecCSFlags flags, CFDictionaryRef attributes, SecStaticCodeRef *staticCode);
OSStatus SecCodeCopySigningInformation(SecStaticCodeRef code, SecCSFlags flags, CFDictionaryRef *information);
CFDataRef SecCertificateCopyExtensionValue(SecCertificateRef certificate, CFTypeRef extensionOID, bool *isCritical);
void SecPolicySetOptionsValue(SecPolicyRef policy, CFStringRef key, CFTypeRef value);
#define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer") #define kCFPreferencesNoContainer CFSTR("kCFPreferencesNoContainer")
typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath); typedef CFPropertyListRef (*_CFPreferencesCopyValueWithContainerType)(CFStringRef key, CFStringRef applicationID, CFStringRef userName, CFStringRef hostName, CFStringRef containerPath);
@ -222,97 +201,6 @@ int runLdid(NSArray* args, NSString** output, NSString** errorOutput)
return WEXITSTATUS(status); return WEXITSTATUS(status);
} }
SecStaticCodeRef getStaticCodeRef(NSString *binaryPath)
{
if(binaryPath == nil)
{
return NULL;
}
CFURLRef binaryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)binaryPath, kCFURLPOSIXPathStyle, false);
if(binaryURL == NULL)
{
NSLog(@"[getStaticCodeRef] failed to get URL to binary %@", binaryPath);
return NULL;
}
SecStaticCodeRef codeRef = NULL;
OSStatus result;
result = SecStaticCodeCreateWithPathAndAttributes(binaryURL, kSecCSDefaultFlags, NULL, &codeRef);
CFRelease(binaryURL);
if(result != errSecSuccess)
{
NSLog(@"[getStaticCodeRef] failed to create static code for binary %@", binaryPath);
return NULL;
}
return codeRef;
}
NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef)
{
if(codeRef == NULL)
{
NSLog(@"[dumpEntitlements] attempting to dump entitlements without a StaticCodeRef");
return nil;
}
CFDictionaryRef signingInfo = NULL;
OSStatus result;
result = SecCodeCopySigningInformation(codeRef, kSecCSRequirementInformation, &signingInfo);
if(result != errSecSuccess)
{
NSLog(@"[dumpEntitlements] failed to copy signing info from static code");
return nil;
}
NSDictionary *entitlementsNSDict = nil;
CFDictionaryRef entitlements = CFDictionaryGetValue(signingInfo, kSecCodeInfoEntitlementsDict);
if(entitlements == NULL)
{
NSLog(@"[dumpEntitlements] no entitlements specified");
}
else if(CFGetTypeID(entitlements) != CFDictionaryGetTypeID())
{
NSLog(@"[dumpEntitlements] invalid entitlements");
}
else
{
entitlementsNSDict = (__bridge NSDictionary *)(entitlements);
NSLog(@"[dumpEntitlements] dumped %@", entitlementsNSDict);
}
CFRelease(signingInfo);
return entitlementsNSDict;
}
NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath)
{
// This function is intended for one-shot checks. Main-event functions should retain/release their own SecStaticCodeRefs
if(binaryPath == nil)
{
return nil;
}
SecStaticCodeRef codeRef = getStaticCodeRef(binaryPath);
if(codeRef == NULL)
{
return nil;
}
NSDictionary *entitlements = dumpEntitlements(codeRef);
CFRelease(codeRef);
return entitlements;
}
BOOL certificateHasDataForExtensionOID(SecCertificateRef certificate, CFStringRef oidString) BOOL certificateHasDataForExtensionOID(SecCertificateRef certificate, CFStringRef oidString)
{ {
if(certificate == NULL || oidString == NULL) if(certificate == NULL || oidString == NULL)
@ -762,7 +650,7 @@ int installApp(NSString* appPath, BOOL sign, BOOL force)
if(suc) if(suc)
{ {
NSLog(@"[installApp] App %@ installed, adding to icon cache now...", appId); NSLog(@"[installApp] App %@ installed, adding to icon cache now...", appId);
registerPath((char*)newAppPath.UTF8String, 0); registerPath((char*)newAppPath.UTF8String, 0, YES);
return 0; return 0;
} }
else else
@ -816,7 +704,7 @@ int uninstallApp(NSString* appPath, NSString* appId)
} }
// unregister app // unregister app
registerPath((char*)appPath.UTF8String, 1); registerPath((char*)appPath.UTF8String, 1, YES);
NSLog(@"[uninstallApp] deleting %@", [appPath stringByDeletingLastPathComponent]); NSLog(@"[uninstallApp] deleting %@", [appPath stringByDeletingLastPathComponent]);
// delete app // delete app
@ -930,7 +818,7 @@ BOOL uninstallTrollStore(BOOL unregister)
if(unregister) if(unregister)
{ {
registerPath((char*)trollStoreAppPath().UTF8String, 1); registerPath((char*)trollStoreAppPath().UTF8String, 1, YES);
} }
return [[NSFileManager defaultManager] removeItemAtPath:trollStore error:nil]; return [[NSFileManager defaultManager] removeItemAtPath:trollStore error:nil];
@ -988,13 +876,13 @@ BOOL installTrollStore(NSString* pathToTar)
void refreshAppRegistrations() void refreshAppRegistrations()
{ {
//registerPath((char*)trollStoreAppPath().UTF8String, 1); //registerPath((char*)trollStoreAppPath().UTF8String, 1, YES);
registerPath((char*)trollStoreAppPath().UTF8String, 0); registerPath((char*)trollStoreAppPath().UTF8String, 0, YES);
for(NSString* appPath in trollStoreInstalledAppBundlePaths()) for(NSString* appPath in trollStoreInstalledAppBundlePaths())
{ {
//registerPath((char*)appPath.UTF8String, 1); //registerPath((char*)appPath.UTF8String, 1, YES);
registerPath((char*)appPath.UTF8String, 0); registerPath((char*)appPath.UTF8String, 0, YES);
} }
} }
@ -1128,6 +1016,59 @@ void registerUserPersistenceHelper(NSString* userAppId)
[[NSFileManager defaultManager] createFileAtPath:markPath contents:[NSData data] attributes:nil]; [[NSFileManager defaultManager] createFileAtPath:markPath contents:[NSData data] attributes:nil];
} }
// Apparently there is some odd behaviour where TrollStore installed apps sometimes get restricted
// This works around that issue at least and is triggered when rebuilding icon cache
void cleanRestrictions(void)
{
NSString* clientTruthPath = @"/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/ClientTruth.plist";
NSURL* clientTruthURL = [NSURL fileURLWithPath:clientTruthPath];
NSDictionary* clientTruthDictionary = [NSDictionary dictionaryWithContentsOfURL:clientTruthURL];
if(!clientTruthDictionary) return;
NSArray* valuesArr;
NSDictionary* lsdAppRemoval = clientTruthDictionary[@"com.apple.lsd.appremoval"];
if(lsdAppRemoval && [lsdAppRemoval isKindOfClass:NSDictionary.class])
{
NSDictionary* clientRestrictions = lsdAppRemoval[@"clientRestrictions"];
if(clientRestrictions && [clientRestrictions isKindOfClass:NSDictionary.class])
{
NSDictionary* unionDict = clientRestrictions[@"union"];
if(unionDict && [unionDict isKindOfClass:NSDictionary.class])
{
NSDictionary* removedSystemAppBundleIDs = unionDict[@"removedSystemAppBundleIDs"];
if(removedSystemAppBundleIDs && [removedSystemAppBundleIDs isKindOfClass:NSDictionary.class])
{
valuesArr = removedSystemAppBundleIDs[@"values"];
}
}
}
}
if(!valuesArr || !valuesArr.count) return;
NSMutableArray* valuesArrM = valuesArr.mutableCopy;
__block BOOL changed = NO;
[valuesArrM enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString* value, NSUInteger idx, BOOL *stop)
{
if(![value hasPrefix:@"com.apple."])
{
[valuesArrM removeObjectAtIndex:idx];
changed = YES;
}
}];
if(!changed) return;
NSMutableDictionary* clientTruthDictionaryM = (__bridge_transfer NSMutableDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)clientTruthDictionary, kCFPropertyListMutableContainersAndLeaves);
clientTruthDictionaryM[@"com.apple.lsd.appremoval"][@"clientRestrictions"][@"union"][@"removedSystemAppBundleIDs"][@"values"] = valuesArrM;
[clientTruthDictionaryM writeToURL:clientTruthURL error:nil];
}
int MAIN_NAME(int argc, char *argv[], char *envp[]) int MAIN_NAME(int argc, char *argv[], char *envp[])
{ {
@autoreleasepool { @autoreleasepool {
@ -1142,12 +1083,10 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
NSString* cmd = [NSString stringWithUTF8String:argv[1]]; NSString* cmd = [NSString stringWithUTF8String:argv[1]];
if([cmd isEqualToString:@"install"]) if([cmd isEqualToString:@"install"])
{ {
NSLog(@"argc = %d", argc);
BOOL force = NO; BOOL force = NO;
if(argc <= 2) return -3; if(argc <= 2) return -3;
if(argc > 3) if(argc > 3)
{ {
NSLog(@"argv3 = %s", argv[3]);
if(!strcmp(argv[3], "force")) if(!strcmp(argv[3], "force"))
{ {
force = YES; force = YES;
@ -1190,8 +1129,10 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
refreshAppRegistrations(); refreshAppRegistrations();
} else if([cmd isEqualToString:@"refresh-all"]) } else if([cmd isEqualToString:@"refresh-all"])
{ {
cleanRestrictions();
[[LSApplicationWorkspace defaultWorkspace] _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:YES]; [[LSApplicationWorkspace defaultWorkspace] _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:YES];
refreshAppRegistrations(); refreshAppRegistrations();
killall(@"backboardd");
} else if([cmd isEqualToString:@"install-persistence-helper"]) } else if([cmd isEqualToString:@"install-persistence-helper"])
{ {
if(argc <= 2) return -3; if(argc <= 2) return -3;
@ -1205,6 +1146,17 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
if(argc <= 2) return -3; if(argc <= 2) return -3;
NSString* userAppId = [NSString stringWithUTF8String:argv[2]]; NSString* userAppId = [NSString stringWithUTF8String:argv[2]];
registerUserPersistenceHelper(userAppId); registerUserPersistenceHelper(userAppId);
} else if([cmd isEqualToString:@"modify-registration"])
{
if(argc <= 3) return -3;
NSString* appPath = [NSString stringWithUTF8String:argv[2]];
NSString* newRegistration = [NSString stringWithUTF8String:argv[3]];
NSString* trollStoreMark = [[appPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"_TrollStore"];
if([[NSFileManager defaultManager] fileExistsAtPath:trollStoreMark])
{
registerPath((char*)appPath.UTF8String, 0, [newRegistration isEqualToString:@"System"]);
}
} }
NSLog(@"returning %d", ret); NSLog(@"returning %d", ret);

View File

@ -1 +1 @@
extern void registerPath(char *path, int unregister); extern void registerPath(char *path, int unregister, BOOL system);

View File

@ -89,7 +89,7 @@ NSDictionary* constructEnvironmentVariablesForContainerPath(NSString* containerP
}; };
} }
void registerPath(char* cPath, int unregister) void registerPath(char* cPath, int unregister, BOOL system)
{ {
if(!cPath) return; if(!cPath) return;
NSString* path = [NSString stringWithUTF8String:cPath]; NSString* path = [NSString stringWithUTF8String:cPath];
@ -129,7 +129,7 @@ void registerPath(char* cPath, int unregister)
// Misc // Misc
dictToRegister[@"ApplicationType"] = @"System"; dictToRegister[@"ApplicationType"] = system ? @"System" : @"User";
dictToRegister[@"CFBundleIdentifier"] = appBundleID; dictToRegister[@"CFBundleIdentifier"] = appBundleID;
dictToRegister[@"CodeInfoIdentifier"] = appBundleID; dictToRegister[@"CodeInfoIdentifier"] = appBundleID;
dictToRegister[@"CompatibilityState"] = @0; dictToRegister[@"CompatibilityState"] = @0;

View File

@ -6,6 +6,7 @@
@interface LSApplicationProxy : LSBundleProxy @interface LSApplicationProxy : LSBundleProxy
+ (instancetype)applicationProxyForIdentifier:(NSString*)identifier; + (instancetype)applicationProxyForIdentifier:(NSString*)identifier;
+ (instancetype)applicationProxyForBundleURL:(NSURL*)bundleURL;
@property NSURL* bundleURL; @property NSURL* bundleURL;
@property NSString* bundleType; @property NSString* bundleType;
@property NSString* canonicalExecutablePath; @property NSString* canonicalExecutablePath;
@ -14,7 +15,8 @@
@property (getter=isInstalled,nonatomic,readonly) BOOL installed; @property (getter=isInstalled,nonatomic,readonly) BOOL installed;
@property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder; @property (getter=isPlaceholder,nonatomic,readonly) BOOL placeholder;
@property (getter=isRestricted,nonatomic,readonly) BOOL restricted; @property (getter=isRestricted,nonatomic,readonly) BOOL restricted;
@property (nonatomic,readonly) NSSet * claimedURLSchemes; @property (nonatomic,readonly) NSSet* claimedURLSchemes;
@property (nonatomic,readonly) NSString* applicationType;
@end @end
@interface LSApplicationWorkspace : NSObject @interface LSApplicationWorkspace : NSObject

View File

@ -9,10 +9,6 @@
- (BOOL)isTrollStore; - (BOOL)isTrollStore;
- (NSString*)getTrollStoreVersion; - (NSString*)getTrollStoreVersion;
- (void)startActivity:(NSString*)activity;
- (void)stopActivityWithCompletion:(void (^)(void))completion;
- (void)downloadTrollStoreAndDo:(void (^)(NSString* localTrollStoreTarPath))doHandler; - (void)downloadTrollStoreAndDo:(void (^)(NSString* localTrollStoreTarPath))doHandler;
- (void)installTrollStorePressed; - (void)installTrollStorePressed;
- (void)updateTrollStorePressed; - (void)updateTrollStorePressed;

View File

@ -1,5 +1,6 @@
#import "TSListControllerShared.h" #import "TSListControllerShared.h"
#import "TSUtil.h" #import "TSUtil.h"
#import "TSPresentationDelegate.h"
@implementation TSListControllerShared @implementation TSListControllerShared
@ -24,34 +25,6 @@
} }
} }
- (void)startActivity:(NSString*)activity
{
if(_activityController) return;
_activityController = [UIAlertController alertControllerWithTitle:activity message:@"" preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(5,5,50,50)];
activityIndicator.hidesWhenStopped = YES;
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
[activityIndicator startAnimating];
[_activityController.view addSubview:activityIndicator];
[self presentViewController:_activityController animated:YES completion:nil];
}
- (void)stopActivityWithCompletion:(void (^)(void))completion
{
if(!_activityController) return;
[_activityController dismissViewControllerAnimated:YES completion:^
{
_activityController = nil;
if(completion)
{
completion();
}
}];
}
- (void)downloadTrollStoreAndDo:(void (^)(NSString* localTrollStoreTarPath))doHandler - (void)downloadTrollStoreAndDo:(void (^)(NSString* localTrollStoreTarPath))doHandler
{ {
NSURL* trollStoreURL = [NSURL URLWithString:@"https://github.com/opa334/TrollStore/releases/latest/download/TrollStore.tar"]; NSURL* trollStoreURL = [NSURL URLWithString:@"https://github.com/opa334/TrollStore/releases/latest/download/TrollStore.tar"];
@ -67,9 +40,9 @@
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:^ [TSPresentationDelegate stopActivityWithCompletion:^
{ {
[self presentViewController:errorAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}]; }];
}); });
} }
@ -89,11 +62,11 @@
{ {
if(update) if(update)
{ {
[self startActivity:@"Updating TrollStore"]; [TSPresentationDelegate startActivity:@"Updating TrollStore"];
} }
else else
{ {
[self startActivity:@"Installing TrollStore"]; [TSPresentationDelegate startActivity:@"Installing TrollStore"];
} }
[self downloadTrollStoreAndDo:^(NSString* tmpTarPath) [self downloadTrollStoreAndDo:^(NSString* tmpTarPath)
@ -113,7 +86,7 @@
{ {
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:^ [TSPresentationDelegate stopActivityWithCompletion:^
{ {
[self reloadSpecifiers]; [self reloadSpecifiers];
}]; }];
@ -124,12 +97,12 @@
{ {
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:^ [TSPresentationDelegate stopActivityWithCompletion:^
{ {
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error installing TrollStore: trollstorehelper returned %d", ret] preferredStyle:UIAlertControllerStyleAlert]; UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error installing TrollStore: trollstorehelper returned %d", ret] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[errorAlert addAction:closeAction]; [errorAlert addAction:closeAction];
[self presentViewController:errorAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}]; }];
}); });
} }
@ -148,7 +121,7 @@
- (void)rebuildIconCachePressed - (void)rebuildIconCachePressed
{ {
[self startActivity:@"Rebuilding Icon Cache"]; [TSPresentationDelegate startActivity:@"Rebuilding Icon Cache"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{ {
@ -156,14 +129,14 @@
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:nil]; [TSPresentationDelegate stopActivityWithCompletion:nil];
}); });
}); });
} }
- (void)refreshAppRegistrationsPressed - (void)refreshAppRegistrationsPressed
{ {
[self startActivity:@"Refreshing"]; [TSPresentationDelegate startActivity:@"Refreshing"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{ {
@ -172,7 +145,7 @@
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:nil]; [TSPresentationDelegate stopActivityWithCompletion:nil];
}); });
}); });
} }
@ -198,7 +171,7 @@
}]; }];
[uninstallWarningAlert addAction:continueAction]; [uninstallWarningAlert addAction:continueAction];
[self presentViewController:uninstallWarningAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:uninstallWarningAlert animated:YES completion:nil];
} }
} }
@ -228,7 +201,7 @@
}]; }];
[uninstallWarningAlert addAction:continueAction]; [uninstallWarningAlert addAction:continueAction];
[self presentViewController:uninstallWarningAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:uninstallWarningAlert animated:YES completion:nil];
} }
@end @end

View File

@ -0,0 +1,10 @@
#import <UIKit/UIKit.h>
@interface TSPresentationDelegate : NSObject
@property (class) UIViewController* presentationViewController;
@property (class) UIAlertController* activityController;
+ (void)startActivity:(NSString*)activity withCancelHandler:(void (^)(void))cancelHandler;
+ (void)startActivity:(NSString*)activity;
+ (void)stopActivityWithCompletion:(void (^)(void))completion;
+ (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion;
@end

View File

@ -0,0 +1,81 @@
#import "TSPresentationDelegate.h"
@implementation TSPresentationDelegate
static UIViewController* g_presentationViewController;
static UIAlertController* g_activityController;
+ (UIViewController*)presentationViewController
{
return g_presentationViewController;
}
+ (void)setPresentationViewController:(UIViewController*)vc
{
g_presentationViewController = vc;
}
+ (UIAlertController*)activityController
{
return g_activityController;
}
+ (void)setActivityController:(UIAlertController*)ac
{
g_activityController = ac;
}
+ (void)startActivity:(NSString*)activity withCancelHandler:(void (^)(void))cancelHandler
{
if(self.activityController)
{
self.activityController.title = activity;
}
else
{
self.activityController = [UIAlertController alertControllerWithTitle:activity message:@"" preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView* activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(5,5,50,50)];
activityIndicator.hidesWhenStopped = YES;
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium;
[activityIndicator startAnimating];
[self.activityController.view addSubview:activityIndicator];
if(cancelHandler)
{
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action)
{
self.activityController = nil;
cancelHandler();
}];
[self.activityController addAction:cancelAction];
}
[self presentViewController:self.activityController animated:YES completion:nil];
}
}
+ (void)startActivity:(NSString*)activity
{
[self startActivity:activity withCancelHandler:nil];
}
+ (void)stopActivityWithCompletion:(void (^)(void))completion
{
if(!self.activityController) return;
[self.activityController dismissViewControllerAnimated:YES completion:^
{
self.activityController = nil;
if(completion)
{
completion();
}
}];
}
+ (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
[self.presentationViewController presentViewController:viewControllerToPresent animated:flag completion:completion];
}
@end

View File

@ -1,6 +1,8 @@
@import Foundation; @import Foundation;
#import "CoreServices.h" #import "CoreServices.h"
#define TrollStoreErrorDomain @"TrollStoreErrorDomain"
extern void chineseWifiFixup(void); extern void chineseWifiFixup(void);
extern void loadMCMFramework(void); extern void loadMCMFramework(void);
extern NSString* safe_getExecutablePath(); extern NSString* safe_getExecutablePath();
@ -8,6 +10,7 @@ extern NSString* rootHelperPath(void);
extern NSString* getNSStringFromFile(int fd); extern NSString* getNSStringFromFile(int fd);
extern void printMultilineNSString(NSString* stringToPrint); extern void printMultilineNSString(NSString* stringToPrint);
extern int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr); extern int spawnRoot(NSString* path, NSArray* args, NSString** stdOut, NSString** stdErr);
extern void killall(NSString* processName);
extern void respring(void); extern void respring(void);
extern void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion)); extern void fetchLatestTrollStoreVersion(void (^completionHandler)(NSString* latestVersion));
@ -16,6 +19,14 @@ extern NSArray* trollStoreInstalledAppContainerPaths();
extern NSString* trollStorePath(); extern NSString* trollStorePath();
extern NSString* trollStoreAppPath(); extern NSString* trollStoreAppPath();
#import <UIKit/UIAlertController.h>
@interface UIAlertController (Private)
@property (setter=_setAttributedTitle:,getter=_attributedTitle,nonatomic,copy) NSAttributedString* attributedTitle;
@property (setter=_setAttributedMessage:,getter=_attributedMessage,nonatomic,copy) NSAttributedString* attributedMessage;
@property (nonatomic,retain) UIImage* image;
@end
typedef enum typedef enum
{ {
PERSISTENCE_HELPER_TYPE_USER = 1 << 0, PERSISTENCE_HELPER_TYPE_USER = 1 << 0,
@ -24,3 +35,27 @@ typedef enum
} PERSISTENCE_HELPER_TYPE; } PERSISTENCE_HELPER_TYPE;
extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes); extern LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedTypes);
typedef struct __SecCode const *SecStaticCodeRef;
typedef CF_OPTIONS(uint32_t, SecCSFlags) {
kSecCSDefaultFlags = 0
};
#define kSecCSRequirementInformation 1 << 2
#define kSecCSSigningInformation 1 << 1
OSStatus SecStaticCodeCreateWithPathAndAttributes(CFURLRef path, SecCSFlags flags, CFDictionaryRef attributes, SecStaticCodeRef *staticCode);
OSStatus SecCodeCopySigningInformation(SecStaticCodeRef code, SecCSFlags flags, CFDictionaryRef *information);
CFDataRef SecCertificateCopyExtensionValue(SecCertificateRef certificate, CFTypeRef extensionOID, bool *isCritical);
void SecPolicySetOptionsValue(SecPolicyRef policy, CFStringRef key, CFTypeRef value);
extern CFStringRef kSecCodeInfoEntitlementsDict;
extern CFStringRef kSecCodeInfoCertificates;
extern CFStringRef kSecPolicyAppleiPhoneApplicationSigning;
extern CFStringRef kSecPolicyAppleiPhoneProfileApplicationSigning;
extern CFStringRef kSecPolicyLeafMarkerOid;
extern SecStaticCodeRef getStaticCodeRef(NSString *binaryPath);
extern NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef);
extern NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath);
extern NSDictionary* dumpEntitlementsFromBinaryData(NSData* binaryData);

View File

@ -344,3 +344,107 @@ LSApplicationProxy* findPersistenceHelperApp(PERSISTENCE_HELPER_TYPE allowedType
return outProxy; return outProxy;
} }
SecStaticCodeRef getStaticCodeRef(NSString *binaryPath)
{
if(binaryPath == nil)
{
return NULL;
}
CFURLRef binaryURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (__bridge CFStringRef)binaryPath, kCFURLPOSIXPathStyle, false);
if(binaryURL == NULL)
{
NSLog(@"[getStaticCodeRef] failed to get URL to binary %@", binaryPath);
return NULL;
}
SecStaticCodeRef codeRef = NULL;
OSStatus result;
result = SecStaticCodeCreateWithPathAndAttributes(binaryURL, kSecCSDefaultFlags, NULL, &codeRef);
CFRelease(binaryURL);
if(result != errSecSuccess)
{
NSLog(@"[getStaticCodeRef] failed to create static code for binary %@", binaryPath);
return NULL;
}
return codeRef;
}
NSDictionary* dumpEntitlements(SecStaticCodeRef codeRef)
{
if(codeRef == NULL)
{
NSLog(@"[dumpEntitlements] attempting to dump entitlements without a StaticCodeRef");
return nil;
}
CFDictionaryRef signingInfo = NULL;
OSStatus result;
result = SecCodeCopySigningInformation(codeRef, kSecCSRequirementInformation, &signingInfo);
if(result != errSecSuccess)
{
NSLog(@"[dumpEntitlements] failed to copy signing info from static code");
return nil;
}
NSDictionary *entitlementsNSDict = nil;
CFDictionaryRef entitlements = CFDictionaryGetValue(signingInfo, kSecCodeInfoEntitlementsDict);
if(entitlements == NULL)
{
NSLog(@"[dumpEntitlements] no entitlements specified");
}
else if(CFGetTypeID(entitlements) != CFDictionaryGetTypeID())
{
NSLog(@"[dumpEntitlements] invalid entitlements");
}
else
{
entitlementsNSDict = (__bridge NSDictionary *)(entitlements);
NSLog(@"[dumpEntitlements] dumped %@", entitlementsNSDict);
}
CFRelease(signingInfo);
return entitlementsNSDict;
}
NSDictionary* dumpEntitlementsFromBinaryAtPath(NSString *binaryPath)
{
// This function is intended for one-shot checks. Main-event functions should retain/release their own SecStaticCodeRefs
if(binaryPath == nil)
{
return nil;
}
SecStaticCodeRef codeRef = getStaticCodeRef(binaryPath);
if(codeRef == NULL)
{
return nil;
}
NSDictionary *entitlements = dumpEntitlements(codeRef);
CFRelease(codeRef);
return entitlements;
}
NSDictionary* dumpEntitlementsFromBinaryData(NSData* binaryData)
{
NSDictionary* entitlements;
NSString* tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
NSURL* tmpURL = [NSURL fileURLWithPath:tmpPath];
if([binaryData writeToURL:tmpURL options:NSDataWritingAtomic error:nil])
{
entitlements = dumpEntitlementsFromBinaryAtPath(tmpPath);
[[NSFileManager defaultManager] removeItemAtURL:tmpURL error:nil];
}
return entitlements;
}

View File

@ -52,7 +52,7 @@
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.2.2</string> <string>1.3</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIDeviceFamily</key> <key>UIDeviceFamily</key>

View File

@ -1,5 +1,6 @@
#import "TSHRootViewController.h" #import "TSHRootViewController.h"
#import <TSUtil.h> #import <TSUtil.h>
#import <TSPresentationDelegate.h>
@implementation TSHRootViewController @implementation TSHRootViewController
@ -11,6 +12,8 @@
- (void)viewDidLoad - (void)viewDidLoad
{ {
[super viewDidLoad]; [super viewDidLoad];
TSPresentationDelegate.presentationViewController = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadSpecifiers) name:UIApplicationWillEnterForegroundNotification object:nil];
fetchLatestTrollStoreVersion(^(NSString* latestVersion) fetchLatestTrollStoreVersion(^(NSString* latestVersion)
@ -123,9 +126,7 @@
[_specifiers addObject:installTrollStoreSpecifier]; [_specifiers addObject:installTrollStoreSpecifier];
} }
NSString* executableName = NSBundle.mainBundle.bundleURL.lastPathComponent; NSString* backupPath = [safe_getExecutablePath() stringByAppendingString:@"_TROLLSTORE_BACKUP"];
NSString* backupExecutableName = [executableName stringByAppendingString:@"_TROLLSTORE_BACKUP"];
NSString* backupPath = [[NSBundle.mainBundle.bundleURL.path stringByDeletingLastPathComponent] stringByAppendingPathComponent:backupExecutableName];
if([[NSFileManager defaultManager] fileExistsAtPath:backupPath]) if([[NSFileManager defaultManager] fileExistsAtPath:backupPath])
{ {
PSSpecifier* uninstallHelperGroupSpecifier = [PSSpecifier emptyGroupSpecifier]; PSSpecifier* uninstallHelperGroupSpecifier = [PSSpecifier emptyGroupSpecifier];

View File

@ -1,6 +1,6 @@
Package: com.opa334.trollstorehelper Package: com.opa334.trollstorehelper
Name: TrollStore Helper Name: TrollStore Helper
Version: 1.2.2 Version: 1.3
Architecture: iphoneos-arm Architecture: iphoneos-arm
Description: Helper utility to install and manage TrollStore! Description: Helper utility to install and manage TrollStore!
Maintainer: opa334 Maintainer: opa334

View File

@ -7,7 +7,8 @@ APPLICATION_NAME = TrollStore
TrollStore_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m) TrollStore_FILES = $(wildcard *.m) $(wildcard ../Shared/*.m)
TrollStore_FRAMEWORKS = UIKit CoreGraphics CoreServices TrollStore_FRAMEWORKS = UIKit CoreGraphics CoreServices
TrollStore_PRIVATE_FRAMEWORKS = Preferences TrollStore_PRIVATE_FRAMEWORKS = Preferences MobileIcons
TrollStore_LIBRARIES = archive
TrollStore_CFLAGS = -fobjc-arc -I../Shared TrollStore_CFLAGS = -fobjc-arc -I../Shared
TrollStore_CODESIGN_FLAGS = -Sentitlements.plist -K../cert.p12 TrollStore_CODESIGN_FLAGS = -Sentitlements.plist -K../cert.p12

View File

@ -50,7 +50,7 @@
<string>iPhoneOS</string> <string>iPhoneOS</string>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.2.2</string> <string>1.3</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UIDeviceFamily</key> <key>UIDeviceFamily</key>
@ -62,81 +62,8 @@
<array> <array>
<string>armv7</string> <string>armv7</string>
</array> </array>
<key>UILaunchImageFile</key> <key>UILaunchStoryboardName</key>
<string>LaunchImage</string> <string>LaunchScreen</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> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
@ -248,6 +175,17 @@
</dict> </dict>
</dict> </dict>
</array> </array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.apple.Magnifier</string>
<key>CFBundleURLSchemes</key>
<array>
<string>apple-magnifier</string>
</array>
</dict>
</array>
<key>LSSupportsOpeningDocumentsInPlace</key> <key>LSSupportsOpeningDocumentsInPlace</key>
<false/> <false/>
<key>TSRootBinaries</key> <key>TSRootBinaries</key>

55
TrollStore/TSAppInfo.h Normal file
View File

@ -0,0 +1,55 @@
//
// TSIPAInfo.h
// IPAInfo
//
// Created by Lars Fröder on 22.10.22.
//
#import <Foundation/Foundation.h>
#import <libarchive/archive.h>
#import <libarchive/archive_entry.h>
@import UIKit;
@interface TSAppInfo : NSObject
{
NSString* _path;
BOOL _isArchive;
struct archive* _archive;
NSString* _cachedAppBundleName;
NSString* _cachedRegistrationState;
NSDictionary* _cachedInfoDictionary;
NSDictionary* _cachedInfoDictionariesByPluginSubpaths;
NSDictionary* _cachedEntitlementsByBinarySubpaths;
UIImage* _cachedPreviewIcon;
int64_t _cachedSize;
}
- (instancetype)initWithIPAPath:(NSString*)ipaPath;
- (instancetype)initWithAppBundlePath:(NSString*)bundlePath;
- (NSError*)determineAppBundleName;
- (NSError*)loadInfoDictionary;
- (NSError*)loadEntitlements;
- (NSError*)loadPreviewIcon;
- (NSError*)sync_loadBasicInfo;
- (NSError*)sync_loadInfo;
- (void)loadBasicInfoWithCompletion:(void (^)(NSError*))completionHandler;
- (void)loadInfoWithCompletion:(void (^)(NSError*))completionHandler;
- (NSString*)displayName;
- (NSString*)bundleIdentifier;
- (NSString*)versionString;
- (NSString*)sizeString;
- (NSString*)bundlePath;
- (NSString*)registrationState;
- (UIImage*)iconForSize:(CGSize)size;
- (NSAttributedString*)detailedInfoTitle;
- (NSAttributedString*)detailedInfoDescription;
//- (UIImage*)image;
- (void)log;
@end

1092
TrollStore/TSAppInfo.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,13 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "TSAppInfo.h"
@interface TSAppTableViewController : UITableViewController @interface TSAppTableViewController : UITableViewController <UISearchResultsUpdating, UIDocumentPickerDelegate>
{ {
UIImage* _placeholderIcon; UIImage* _placeholderIcon;
NSArray* _cachedAppPaths; NSArray<TSAppInfo*>* _cachedAppInfos;
NSMutableDictionary* _cachedIcons; NSMutableDictionary* _cachedIcons;
UISearchController* _searchController;
NSString* _searchKey;
} }
@end @end

View File

@ -1,6 +1,10 @@
#import "TSAppTableViewController.h" #import "TSAppTableViewController.h"
#import "TSApplicationsManager.h" #import "TSApplicationsManager.h"
#import <TSPresentationDelegate.h>
#import "TSInstallationController.h"
#import "TSUtil.h"
@import UniformTypeIdentifiers;
#define ICON_FORMAT_IPAD 8 #define ICON_FORMAT_IPAD 8
#define ICON_FORMAT_IPHONE 10 #define ICON_FORMAT_IPHONE 10
@ -34,16 +38,37 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
@implementation TSAppTableViewController @implementation TSAppTableViewController
- (void)loadCachedAppPaths - (void)loadAppInfos
{ {
NSArray* appPaths = [[TSApplicationsManager sharedInstance] installedAppPaths]; NSArray* appPaths = [[TSApplicationsManager sharedInstance] installedAppPaths];
NSMutableArray<TSAppInfo*>* appInfos = [NSMutableArray new];
_cachedAppPaths = [appPaths sortedArrayUsingComparator:^NSComparisonResult(NSString* appPathA, NSString* appPathB) { for(NSString* appPath in appPaths)
NSString* displayNameA = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPathA]; {
NSString* displayNameB = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPathB]; TSAppInfo* appInfo = [[TSAppInfo alloc] initWithAppBundlePath:appPath];
[appInfo sync_loadBasicInfo];
[appInfos addObject:appInfo];
}
return [displayNameA localizedStandardCompare:displayNameB]; if(_searchKey && ![_searchKey isEqualToString:@""])
{
[appInfos enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(TSAppInfo* appInfo, NSUInteger idx, BOOL* stop)
{
NSString* appName = [appInfo displayName];
BOOL nameMatch = [appName rangeOfString:_searchKey options:NSCaseInsensitiveSearch range:NSMakeRange(0, [appName length]) locale:[NSLocale currentLocale]].location != NSNotFound;
if(!nameMatch)
{
[appInfos removeObjectAtIndex:idx];
}
}]; }];
}
[appInfos sortUsingComparator:^(TSAppInfo* appInfoA, TSAppInfo* appInfoB)
{
return [[appInfoA displayName] localizedStandardCompare:[appInfoB displayName]];
}];
_cachedAppInfos = appInfos.copy;
} }
- (instancetype)init - (instancetype)init
@ -51,7 +76,7 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
self = [super init]; self = [super init];
if(self) if(self)
{ {
[self loadCachedAppPaths]; [self loadAppInfos];
_placeholderIcon = [UIImage _applicationIconImageForBundleIdentifier:@"com.apple.WebSheet" format:iconFormatToUse() scale:[UIScreen mainScreen].scale]; _placeholderIcon = [UIImage _applicationIconImageForBundleIdentifier:@"com.apple.WebSheet" format:iconFormatToUse() scale:[UIScreen mainScreen].scale];
_cachedIcons = [NSMutableDictionary new]; _cachedIcons = [NSMutableDictionary new];
} }
@ -60,7 +85,7 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
- (void)reloadTable - (void)reloadTable
{ {
[self loadCachedAppPaths]; [self loadAppInfos];
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self.tableView reloadData]; [self.tableView reloadData];
@ -70,42 +95,187 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
- (void)loadView - (void)loadView
{ {
[super loadView]; [super loadView];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadTable) name:@"ApplicationsChanged" object:nil];
selector:@selector(reloadTable)
name:@"ApplicationsChanged"
object:nil];
} }
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
self.tableView.allowsMultipleSelectionDuringEditing = NO; self.tableView.allowsMultipleSelectionDuringEditing = NO;
[self _setUpNavigationBar];
[self _setUpSearchBar];
} }
- (void)showError:(NSError*)error - (void)_setUpNavigationBar
{ {
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; UIAction* installFromFileAction = [UIAction actionWithTitle:@"Install IPA File" image:[UIImage systemImageNamed:@"doc.badge.plus"] identifier:@"InstallIPAFile" handler:^(__kindof UIAction *action)
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil]; {
[errorAlert addAction:closeAction]; UTType* ipaType = [UTType typeWithFilenameExtension:@"ipa" conformingToType:UTTypeData];
[self presentViewController:errorAlert animated:YES completion:nil]; UTType* tipaType = [UTType typeWithFilenameExtension:@"tipa" conformingToType:UTTypeData];
UIDocumentPickerViewController* documentPickerVC = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[ipaType, tipaType]];
//UIDocumentPickerViewController* documentPickerVC = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.apple.itunes.ipa", @"com.opa334.trollstore.tipa"] inMode:UIDocumentPickerModeOpen];
documentPickerVC.allowsMultipleSelection = NO;
documentPickerVC.delegate = self;
[TSPresentationDelegate presentViewController:documentPickerVC animated:YES completion:nil];
}];
UIAction* installFromURLAction = [UIAction actionWithTitle:@"Install from URL" image:[UIImage systemImageNamed:@"link.badge.plus"] identifier:@"InstallFromURL" handler:^(__kindof UIAction *action)
{
UIAlertController* installURLController = [UIAlertController alertControllerWithTitle:@"Install from URL" message:@"" preferredStyle:UIAlertControllerStyleAlert];
[installURLController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = @"URL";
}];
UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{
NSString* URLString = installURLController.textFields.firstObject.text;
NSURL* remoteURL = [NSURL URLWithString:URLString];
[TSInstallationController handleAppInstallFromRemoteURL:remoteURL completion:nil];
}];
[installURLController addAction:installAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[installURLController addAction:cancelAction];
[TSPresentationDelegate presentViewController:installURLController animated:YES completion:nil];
}];
UIMenu* installMenu = [UIMenu menuWithChildren:@[installFromFileAction, installFromURLAction]];
UIBarButtonItem* installBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"plus"] menu:installMenu];
self.navigationItem.rightBarButtonItems = @[installBarButtonItem];
} }
- (void)openAppPressedForRowAtIndexPath:(NSIndexPath *)indexPath - (void)_setUpSearchBar
{
_searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
_searchController.searchResultsUpdater = self;
_searchController.obscuresBackgroundDuringPresentation = NO;
self.navigationItem.searchController = _searchController;
self.navigationItem.hidesSearchBarWhenScrolling = YES;
}
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_searchKey = searchController.searchBar.text;
[self reloadTable];
});
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
{
NSString* pathToIPA = urls.firstObject.path;
[TSInstallationController presentInstallationAlertForFile:pathToIPA completion:nil];
}
- (void)openAppPressedForRowAtIndexPath:(NSIndexPath*)indexPath
{ {
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance];
NSString* appPath = _cachedAppPaths[indexPath.row]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
NSString* appId = [appsManager appIdForAppPath:appPath]; NSString* appId = [appInfo bundleIdentifier];
BOOL didOpen = [appsManager openApplicationWithBundleID:appId]; BOOL didOpen = [appsManager openApplicationWithBundleID:appId];
// if we failed to open the app, show an alert // if we failed to open the app, show an alert
if (!didOpen) { if(!didOpen)
NSString *failMessage = [NSString stringWithFormat: @"Failed to open %@", appId]; {
UIAlertController* didFailController = [UIAlertController alertControllerWithTitle:failMessage message: nil preferredStyle:UIAlertControllerStyleAlert]; NSString* failMessage = @"";
if([[appInfo registrationState] isEqualToString:@"User"])
{
failMessage = @"This app was not able to launch because it has a \"User\" registration state, register it as \"System\" and try again.";
}
NSString* failTitle = [NSString stringWithFormat:@"Failed to open %@", appId];
UIAlertController* didFailController = [UIAlertController alertControllerWithTitle:failTitle message:failMessage preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[didFailController addAction: cancelAction]; [didFailController addAction:cancelAction];
[self presentViewController:didFailController animated:YES completion:nil]; [TSPresentationDelegate presentViewController:didFailController animated:YES completion:nil];
}
}
- (void)showDetailsPressedForRowAtIndexPath:(NSIndexPath*)indexPath
{
TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
[appInfo loadInfoWithCompletion:^(NSError* error)
{
dispatch_async(dispatch_get_main_queue(), ^
{
if(!error)
{
UIAlertController* detailsAlert = [UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleAlert];
detailsAlert.attributedTitle = [appInfo detailedInfoTitle];
detailsAlert.attributedMessage = [appInfo detailedInfoDescription];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[detailsAlert addAction:closeAction];
[TSPresentationDelegate presentViewController:detailsAlert animated:YES completion:nil];
}
else
{
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Parse Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[errorAlert addAction:closeAction];
[TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}
});
}];
}
- (void)changeAppRegistrationForRowAtIndexPath:(NSIndexPath*)indexPath toState:(NSString*)newState
{
TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
if([newState isEqualToString:@"User"])
{
NSString* title = [NSString stringWithFormat:@"Switching '%@' to \"User\" Registration", [appInfo displayName]];
UIAlertController* confirmationAlert = [UIAlertController alertControllerWithTitle:title message:@"Switching this app to a \"User\" registration will make it unlaunchable after the next respring because the bugs exploited in TrollStore only affect apps registered as \"System\".\nThe purpose of this option is to make the app temporarily show up in settings, so you can adjust the settings and then switch it back to a \"System\" registration (TrollStore installed apps do not show up in settings otherwise). Additionally, the \"User\" registration state is also useful to temporarily fix iTunes file sharing, which also doesn't work for TrollStore installed apps otherwise.\nWhen you're done making the changes you need and want the app to become launchable again, you will need to switch it back to \"System\" state in TrollStore." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* switchToUserAction = [UIAlertAction actionWithTitle:@"Switch to \"User\"" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action)
{
[[TSApplicationsManager sharedInstance] changeAppRegistration:[appInfo bundlePath] toState:newState];
[appInfo sync_loadBasicInfo];
}];
[confirmationAlert addAction:switchToUserAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[confirmationAlert addAction:cancelAction];
[TSPresentationDelegate presentViewController:confirmationAlert animated:YES completion:nil];
}
else
{
[[TSApplicationsManager sharedInstance] changeAppRegistration:[appInfo bundlePath] toState:newState];
[appInfo sync_loadBasicInfo];
NSString* title = [NSString stringWithFormat:@"Switched '%@' to \"System\" Registration", [appInfo displayName]];
UIAlertController* infoAlert = [UIAlertController alertControllerWithTitle:title message:@"The app has been switched to the \"System\" registration state and will become launchable again after a respring." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* respringAction = [UIAlertAction actionWithTitle:@"Respring" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{
respring();
}];
[infoAlert addAction:respringAction];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[infoAlert addAction:closeAction];
[TSPresentationDelegate presentViewController:infoAlert animated:YES completion:nil];
} }
} }
@ -113,9 +283,11 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
{ {
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance];
NSString* appPath = _cachedAppPaths[indexPath.row]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
NSString* appId = [appsManager appIdForAppPath:appPath];
NSString* appName = [appsManager displayNameForAppPath:appPath]; NSString* appPath = [appInfo bundlePath];
NSString* appId = [appInfo bundleIdentifier];
NSString* appName = [appInfo displayName];
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]; 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];
@ -135,7 +307,7 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[confirmAlert addAction:cancelAction]; [confirmAlert addAction:cancelAction];
[self presentViewController:confirmAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:confirmAlert animated:YES completion:nil];
} }
- (void)deselectRow - (void)deselectRow
@ -150,7 +322,7 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _cachedAppPaths.count; return _cachedAppInfos.count;
} }
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
@ -164,12 +336,12 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ApplicationCell"]; cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"ApplicationCell"];
} }
NSString* appPath = _cachedAppPaths[indexPath.row]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
NSString* appId = [[TSApplicationsManager sharedInstance] appIdForAppPath:appPath]; NSString* appId = [appInfo bundleIdentifier];
NSString* appVersion = [[TSApplicationsManager sharedInstance] versionStringForAppPath:appPath]; NSString* appVersion = [appInfo versionString];
// Configure the cell... // Configure the cell...
cell.textLabel.text = [[TSApplicationsManager sharedInstance] displayNameForAppPath:appPath]; cell.textLabel.text = [appInfo displayName];
cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ • %@", appVersion, appId]; cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ • %@", appVersion, appId];
cell.imageView.layer.borderWidth = 1; cell.imageView.layer.borderWidth = 1;
cell.imageView.layer.borderColor = [UIColor.labelColor colorWithAlphaComponent:0.1].CGColor; cell.imageView.layer.borderColor = [UIColor.labelColor colorWithAlphaComponent:0.1].CGColor;
@ -228,32 +400,47 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{ {
TSApplicationsManager* appsManager = [TSApplicationsManager sharedInstance]; TSAppInfo* appInfo = _cachedAppInfos[indexPath.row];
NSString* appPath = _cachedAppPaths[indexPath.row]; NSString* appId = [appInfo bundleIdentifier];
NSString* appId = [appsManager appIdForAppPath:appPath]; NSString* appName = [appInfo displayName];
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];*/
UIAlertController* appSelectAlert = [UIAlertController alertControllerWithTitle:appName?:@"" message:appId?:@"" preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* openAction = [UIAlertAction actionWithTitle:@"Open" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) UIAlertAction* openAction = [UIAlertAction actionWithTitle:@"Open" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{ {
[self openAppPressedForRowAtIndexPath:indexPath]; [self openAppPressedForRowAtIndexPath:indexPath];
[self deselectRow]; [self deselectRow];
}]; }];
[appSelectAlert addAction: openAction]; [appSelectAlert addAction:openAction];
UIAlertAction* showDetailsAction = [UIAlertAction actionWithTitle:@"Show Details" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{
[self showDetailsPressedForRowAtIndexPath:indexPath];
[self deselectRow];
}];
[appSelectAlert addAction:showDetailsAction];
NSString* switchState;
NSString* registrationState = [appInfo registrationState];
UIAlertActionStyle switchActionStyle = 0;
if([registrationState isEqualToString:@"System"])
{
switchState = @"User";
switchActionStyle = UIAlertActionStyleDestructive;
}
else if([registrationState isEqualToString:@"User"])
{
switchState = @"System";
switchActionStyle = UIAlertActionStyleDefault;
}
UIAlertAction* switchRegistrationAction = [UIAlertAction actionWithTitle:[NSString stringWithFormat:@"Switch to \"%@\" Registration", switchState] style:switchActionStyle handler:^(UIAlertAction* action)
{
[self changeAppRegistrationForRowAtIndexPath:indexPath toState:switchState];
[self deselectRow];
}];
[appSelectAlert addAction:switchRegistrationAction];
UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall App" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) UIAlertAction* uninstallAction = [UIAlertAction actionWithTitle:@"Uninstall App" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action)
{ {
@ -271,7 +458,7 @@ UIImage* imageWithSize(UIImage* image, CGSize size)
appSelectAlert.popoverPresentationController.sourceView = tableView; appSelectAlert.popoverPresentationController.sourceView = tableView;
appSelectAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath]; appSelectAlert.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
[self presentViewController:appSelectAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:appSelectAlert animated:YES completion:nil];
} }
@end @end

View File

@ -9,10 +9,6 @@
+ (instancetype)sharedInstance; + (instancetype)sharedInstance;
- (NSArray*)installedAppPaths; - (NSArray*)installedAppPaths;
- (NSDictionary*)infoDictionaryForAppPath:(NSString*)appPath;
- (NSString*)appIdForAppPath:(NSString*)appPath;
- (NSString*)displayNameForAppPath:(NSString*)appPath;
- (NSString*)versionStringForAppPath:(NSString*)appPath;
- (NSError*)errorForCode:(int)code; - (NSError*)errorForCode:(int)code;
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut; - (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut;
@ -20,6 +16,6 @@
- (int)uninstallApp:(NSString*)appId; - (int)uninstallApp:(NSString*)appId;
- (int)uninstallAppByPath:(NSString*)path; - (int)uninstallAppByPath:(NSString*)path;
- (BOOL)openApplicationWithBundleID:(NSString *)appID; - (BOOL)openApplicationWithBundleID:(NSString *)appID;
//- (int)detachFromApp:(NSString*)appId; - (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState;
@end @end

View File

@ -1,8 +1,6 @@
#import "TSApplicationsManager.h" #import "TSApplicationsManager.h"
#import <TSUtil.h> #import <TSUtil.h>
#define TrollStoreErrorDomain @"TrollStoreErrorDomain"
@implementation TSApplicationsManager @implementation TSApplicationsManager
+ (instancetype)sharedInstance + (instancetype)sharedInstance
@ -20,55 +18,6 @@
return trollStoreInstalledAppBundlePaths(); 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 - (NSError*)errorForCode:(int)code
{ {
NSString* errorDescription = @"Unknown Error"; NSString* errorDescription = @"Unknown Error";
@ -163,12 +112,10 @@
return [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:appId]; return [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:appId];
} }
/*- (int)detachFromApp:(NSString*)appId - (int)changeAppRegistration:(NSString*)appPath toState:(NSString*)newState
{ {
if(!appId) return -200; if(!appPath || !newState) return -200;
int ret = spawnRoot(rootHelperPath(), @[@"detach", appId], nil, nil); return spawnRoot(rootHelperPath(), @[@"modify-registration", appPath, newState], nil, nil);
[[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil]; }
return ret;
}*/
@end @end

View File

@ -0,0 +1,12 @@
@import Foundation;
@interface TSInstallationController : NSObject
+ (void)presentInstallationAlertForFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completion;
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completion;
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completion;
+ (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completion;
@end

View File

@ -0,0 +1,168 @@
#import "TSInstallationController.h"
#import "TSApplicationsManager.h"
#import "TSAppInfo.h"
#import <TSUtil.h>
#import <TSPresentationDelegate.h>
@implementation TSInstallationController
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completionBlock
{
dispatch_async(dispatch_get_main_queue(), ^
{
[TSPresentationDelegate startActivity:@"Installing"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
// Install IPA
//NSString* log;
int ret = [[TSApplicationsManager sharedInstance] installIpa:pathToIPA 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(), ^
{
[TSPresentationDelegate stopActivityWithCompletion:^
{
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)
{
if(completionBlock) completionBlock(NO, error);
}
}];
[errorAlert addAction:closeAction];
if(ret == 171)
{
UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{
[self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock];
}];
[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];*/
}
[TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}
if(ret != 171)
{
if(completionBlock) completionBlock((BOOL)error, error);
}
}];
});
});
});
}
+ (void)presentInstallationAlertForFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completionBlock
{
TSAppInfo* appInfo = [[TSAppInfo alloc] initWithIPAPath:pathToIPA];
[appInfo loadInfoWithCompletion:^(NSError* error)
{
dispatch_async(dispatch_get_main_queue(), ^
{
if(!error)
{
UIAlertController* installAlert = [UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleAlert];
installAlert.attributedTitle = [appInfo detailedInfoTitle];
installAlert.attributedMessage = [appInfo detailedInfoDescription];
UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action)
{
[self handleAppInstallFromFile:pathToIPA completion:completionBlock];
}];
[installAlert addAction:installAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action)
{
completionBlock(NO, nil);
}];
[installAlert addAction:cancelAction];
[TSPresentationDelegate presentViewController:installAlert animated:YES completion:nil];
}
else
{
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Parse Error %ld", error.code] message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[errorAlert addAction:closeAction];
[TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}
});
}];
}
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completionBlock
{
[self handleAppInstallFromFile:pathToIPA forceInstall:NO completion:completionBlock];
}
+ (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completionBlock
{
NSURLRequest* downloadRequest = [NSURLRequest requestWithURL:remoteURL];
dispatch_async(dispatch_get_main_queue(), ^
{
NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:downloadRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{
dispatch_async(dispatch_get_main_queue(), ^
{
[TSPresentationDelegate stopActivityWithCompletion:^
{
if(error)
{
UIAlertController* errorAlert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"Error downloading app: %@", error] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* closeAction = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:nil];
[errorAlert addAction:closeAction];
[TSPresentationDelegate presentViewController:errorAlert animated:YES completion:^
{
if(completionBlock) completionBlock(NO, error);
}];
}
else
{
NSString* tmpIpaPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.ipa"];
[[NSFileManager defaultManager] removeItemAtPath:tmpIpaPath error:nil];
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:tmpIpaPath error:nil];
[self presentInstallationAlertForFile:tmpIpaPath completion:^(BOOL success, NSError* error)
{
[[NSFileManager defaultManager] removeItemAtPath:tmpIpaPath error:nil];
if(completionBlock) completionBlock(success, error);
}];
}
}];
});
}];
[TSPresentationDelegate startActivity:@"Downloading" withCancelHandler:^
{
[downloadTask cancel];
}];
[downloadTask resume];
});
}
//+ (void)showInstallAppAlertForFile:(NSString*)pathToIPA
@end

View File

@ -1,6 +1,7 @@
#import "TSRootViewController.h" #import "TSRootViewController.h"
#import "TSAppTableViewController.h" #import "TSAppTableViewController.h"
#import "TSSettingsListController.h" #import "TSSettingsListController.h"
#import <TSPresentationDelegate.h>
@implementation TSRootViewController @implementation TSRootViewController
@ -23,4 +24,11 @@
self.viewControllers = @[appNavigationController, settingsNavigationController]; self.viewControllers = @[appNavigationController, settingsNavigationController];
} }
- (void)viewDidLoad
{
[super viewDidLoad];
TSPresentationDelegate.presentationViewController = self;
}
@end @end

View File

@ -1,98 +1,24 @@
#import "TSSceneDelegate.h" #import "TSSceneDelegate.h"
#import "TSRootViewController.h" #import "TSRootViewController.h"
#import "TSUtil.h" #import "TSUtil.h"
#import "TSApplicationsManager.h" #import "TSInstallationController.h"
#import <TSPresentationDelegate.h>
@implementation TSSceneDelegate @implementation TSSceneDelegate
- (void)doIPAInstall:(NSString*)ipaPath scene:(UIWindowScene*)scene force:(BOOL)force completion:(void (^)(void))completion - (void)handleURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts scene:(UIWindowScene*)scene
{
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) for(UIOpenURLContext* context in URLContexts)
{ {
NSLog(@"openURLContexts %@", context.URL); NSLog(@"openURLContexts %@", context.URL);
NSURL* url = context.URL; NSURL* url = context.URL;
if (url != nil && [url isFileURL]) {
if(url)
{
NSLog(@"ts_test url: %@", url);
NSLog(@"ts_test url.scheme: %@", url.scheme);
if([url isFileURL])
{
[url startAccessingSecurityScopedResource]; [url startAccessingSecurityScopedResource];
void (^doneBlock)(BOOL) = ^(BOOL shouldExit) void (^doneBlock)(BOOL) = ^(BOOL shouldExit)
{ {
@ -109,7 +35,7 @@
if ([url.pathExtension.lowercaseString isEqualToString:@"ipa"] || [url.pathExtension.lowercaseString isEqualToString:@"tipa"]) if ([url.pathExtension.lowercaseString isEqualToString:@"ipa"] || [url.pathExtension.lowercaseString isEqualToString:@"tipa"])
{ {
[self doIPAInstall:url.path scene:(UIWindowScene*)scene force:NO completion:^{ [TSInstallationController presentInstallationAlertForFile:url.path completion:^(BOOL success, NSError* error){
doneBlock(NO); doneBlock(NO);
}]; }];
} }
@ -122,6 +48,31 @@
NSLog(@"Updated TrollStore!"); NSLog(@"Updated TrollStore!");
} }
} }
else if([url.scheme isEqualToString:@"apple-magnifier"])
{
NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
if([components.host isEqualToString:@"install"])
{
NSString* URLStringToInstall;
for(NSURLQueryItem* queryItem in components.queryItems)
{
NSLog(@"ts_test queryItem %@ = %@", queryItem.name, queryItem.value);
if([queryItem.name isEqualToString:@"url"])
{
URLStringToInstall = queryItem.value;
break;
}
}
if(URLStringToInstall && [URLStringToInstall isKindOfClass:NSString.class])
{
NSURL* URLToInstall = [NSURL URLWithString:URLStringToInstall];
[TSInstallationController handleAppInstallFromRemoteURL:URLToInstall completion:nil];
}
}
}
}
} }
} }

View File

@ -1,6 +1,7 @@
#import "TSSettingsListController.h" #import "TSSettingsListController.h"
#import <TSUtil.h> #import <TSUtil.h>
#import <Preferences/PSSpecifier.h> #import <Preferences/PSSpecifier.h>
#import <TSPresentationDelegate.h>
@implementation TSSettingsListController @implementation TSSettingsListController
@ -239,7 +240,7 @@
NSURL* ldidURL = [NSURL URLWithString:@"https://github.com/opa334/ldid/releases/download/v2.1.5-procursus5/ldid"]; NSURL* ldidURL = [NSURL URLWithString:@"https://github.com/opa334/ldid/releases/download/v2.1.5-procursus5/ldid"];
NSURLRequest* ldidRequest = [NSURLRequest requestWithURL:ldidURL]; NSURLRequest* ldidRequest = [NSURLRequest requestWithURL:ldidURL];
[self startActivity:@"Installing ldid"]; [TSPresentationDelegate startActivity:@"Installing ldid"];
NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:ldidRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) NSURLSessionDownloadTask* downloadTask = [NSURLSession.sharedSession downloadTaskWithRequest:ldidRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)
{ {
@ -251,9 +252,9 @@
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:^ [TSPresentationDelegate stopActivityWithCompletion:^
{ {
[self presentViewController:errorAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:errorAlert animated:YES completion:nil];
}]; }];
}); });
} }
@ -262,7 +263,7 @@
spawnRoot(rootHelperPath(), @[@"install-ldid", location.path], nil, nil); spawnRoot(rootHelperPath(), @[@"install-ldid", location.path], nil, nil);
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
[self stopActivityWithCompletion:nil]; [TSPresentationDelegate stopActivityWithCompletion:nil];
[self reloadSpecifiers]; [self reloadSpecifiers];
}); });
} }
@ -309,7 +310,7 @@
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil];
[selectAppAlert addAction:cancelAction]; [selectAppAlert addAction:cancelAction];
[self presentViewController:selectAppAlert animated:YES completion:nil]; [TSPresentationDelegate presentViewController:selectAppAlert animated:YES completion:nil];
} }
- (void)doTheDashPressed - (void)doTheDashPressed

View File

@ -1,6 +1,6 @@
Package: com.opa334.trollstore Package: com.opa334.trollstore
Name: TrollStore Name: TrollStore
Version: 1.2.2 Version: 1.3
Architecture: iphoneos-arm Architecture: iphoneos-arm
Description: An awesome application! Description: An awesome application!
Maintainer: opa334 Maintainer: opa334

View File

@ -37,5 +37,7 @@
<string>data-allowed-write</string> <string>data-allowed-write</string>
<string>preferences-write</string> <string>preferences-write</string>
</array> </array>
<key>com.apple.springboard.opensensitiveurl</key>
<true/>
</dict> </dict>
</plist> </plist>