1
0
mirror of https://github.com/opa334/TrollStore.git synced 2026-02-22 12:12:24 +08:00

stealth install of TrollStore and user apps

This commit is contained in:
GZZ 2026-02-21 20:56:58 +08:00
parent d11c04666a
commit 48ba54ca67
8 changed files with 215 additions and 69 deletions

View File

@ -807,7 +807,13 @@ int signApp(NSString* appPath)
} }
#endif #endif
void applyPatchesToInfoDictionary(NSString* appPath) NSString* randomStealthBundleIdentifierSuffix(void)
{
uint64_t randomValue = ((uint64_t)arc4random() << 32) | arc4random();
return [NSString stringWithFormat:@"TS_%016llX", randomValue];
}
void applyPatchesToInfoDictionary(NSString* appPath, BOOL stealthInstall, BOOL stripURLSchemesInStealth)
{ {
NSURL* appURL = [NSURL fileURLWithPath:appPath]; NSURL* appURL = [NSURL fileURLWithPath:appPath];
NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"]; NSURL* infoPlistURL = [appURL URLByAppendingPathComponent:@"Info.plist"];
@ -817,41 +823,84 @@ void applyPatchesToInfoDictionary(NSString* appPath)
// Enable Notifications // Enable Notifications
infoDictM[@"SBAppUsesLocalNotifications"] = @1; infoDictM[@"SBAppUsesLocalNotifications"] = @1;
// Remove system claimed URL schemes if existant if(stealthInstall)
NSSet* appleSchemes = systemURLSchemes();
NSArray* CFBundleURLTypes = infoDictM[@"CFBundleURLTypes"];
if([CFBundleURLTypes isKindOfClass:[NSArray class]])
{ {
NSMutableArray* CFBundleURLTypesM = [NSMutableArray new]; NSString* originalAppId = infoDictM[@"TSOriginalBundleIdentifier"];
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0)
for(NSDictionary* URLType in CFBundleURLTypes)
{ {
if(![URLType isKindOfClass:[NSDictionary class]]) continue; originalAppId = infoDictM[@"CFBundleIdentifier"];
NSMutableDictionary* modifiedURLType = URLType.mutableCopy;
NSArray* URLSchemes = URLType[@"CFBundleURLSchemes"];
if(URLSchemes)
{
NSMutableSet* URLSchemesSet = [NSMutableSet setWithArray:URLSchemes];
for(NSString* existingURLScheme in [URLSchemesSet copy])
{
if(![existingURLScheme isKindOfClass:[NSString class]])
{
[URLSchemesSet removeObject:existingURLScheme];
continue;
}
if([appleSchemes containsObject:existingURLScheme.lowercaseString])
{
[URLSchemesSet removeObject:existingURLScheme];
}
}
modifiedURLType[@"CFBundleURLSchemes"] = [URLSchemesSet allObjects];
}
[CFBundleURLTypesM addObject:modifiedURLType.copy];
} }
infoDictM[@"CFBundleURLTypes"] = CFBundleURLTypesM.copy; if([originalAppId isKindOfClass:NSString.class] && originalAppId.length > 0)
{
NSString* stealthAppIdToUse = installedStealthAppIdForOriginalAppId(originalAppId);
if(!stealthAppIdToUse)
{
NSString* currentAppId = infoDictM[@"CFBundleIdentifier"];
NSString* currentOriginalAppId = infoDictM[@"TSOriginalBundleIdentifier"];
BOOL hasExistingStealthIdInInfoPlist = [currentAppId isKindOfClass:NSString.class] &&
currentAppId.length > 0 &&
![currentAppId isEqualToString:originalAppId] &&
[currentOriginalAppId isKindOfClass:NSString.class] &&
[currentOriginalAppId isEqualToString:originalAppId];
if(hasExistingStealthIdInInfoPlist)
{
stealthAppIdToUse = currentAppId;
}
else
{
stealthAppIdToUse = [originalAppId stringByAppendingFormat:@".%@", randomStealthBundleIdentifierSuffix()];
}
}
infoDictM[@"TSOriginalBundleIdentifier"] = originalAppId;
infoDictM[@"CFBundleIdentifier"] = stealthAppIdToUse;
}
if(stripURLSchemesInStealth)
{
// Strip URL schemes in stealth mode.
[infoDictM removeObjectForKey:@"CFBundleURLTypes"];
}
}
else
{
// Remove system claimed URL schemes if existant
NSSet* appleSchemes = systemURLSchemes();
NSArray* CFBundleURLTypes = infoDictM[@"CFBundleURLTypes"];
if([CFBundleURLTypes isKindOfClass:[NSArray class]])
{
NSMutableArray* CFBundleURLTypesM = [NSMutableArray new];
for(NSDictionary* URLType in CFBundleURLTypes)
{
if(![URLType isKindOfClass:[NSDictionary class]]) continue;
NSMutableDictionary* modifiedURLType = URLType.mutableCopy;
NSArray* URLSchemes = URLType[@"CFBundleURLSchemes"];
if(URLSchemes)
{
NSMutableSet* URLSchemesSet = [NSMutableSet setWithArray:URLSchemes];
for(NSString* existingURLScheme in [URLSchemesSet copy])
{
if(![existingURLScheme isKindOfClass:[NSString class]])
{
[URLSchemesSet removeObject:existingURLScheme];
continue;
}
if([appleSchemes containsObject:existingURLScheme.lowercaseString])
{
[URLSchemesSet removeObject:existingURLScheme];
}
}
modifiedURLType[@"CFBundleURLSchemes"] = [URLSchemesSet allObjects];
}
[CFBundleURLTypesM addObject:modifiedURLType.copy];
}
infoDictM[@"CFBundleURLTypes"] = CFBundleURLTypesM.copy;
}
} }
[infoDictM writeToURL:infoPlistURL error:nil]; [infoDictM writeToURL:infoPlistURL error:nil];
@ -864,29 +913,54 @@ void applyPatchesToInfoDictionary(NSString* appPath)
// 174: // 174:
// 180: tried to sign app where the main binary is encrypted // 180: tried to sign app where the main binary is encrypted
// 184: tried to sign app where an additional binary is encrypted // 184: tried to sign app where an additional binary is encrypted
// 186: stealth/non-stealth install mode conflicts with an existing install
int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, BOOL useInstalldMethod, BOOL skipUICache) int installApp(NSString* appPackagePath, BOOL sign, BOOL force, BOOL isTSUpdate, BOOL useInstalldMethod, BOOL skipUICache, BOOL stealthInstall, BOOL stripURLSchemesInStealth)
{ {
NSLog(@"[installApp force = %d]", force); NSLog(@"[installApp force = %d, stealthInstall = %d]", force, stealthInstall);
NSString* appPayloadPath = [appPackagePath stringByAppendingPathComponent:@"Payload"]; NSString* appPayloadPath = [appPackagePath stringByAppendingPathComponent:@"Payload"];
NSString* appBundleToInstallPath = findAppPathInBundlePath(appPayloadPath); NSString* appBundleToInstallPath = findAppPathInBundlePath(appPayloadPath);
if(!appBundleToInstallPath) return 167; if(!appBundleToInstallPath) return 167;
NSString* appId = appIdForAppPath(appBundleToInstallPath); NSDictionary* appInfoDict = infoDictionaryForAppPath(appBundleToInstallPath);
if(!appId) return 176; if(!appInfoDict) return 172;
if(([appId.lowercaseString isEqualToString:@"com.opa334.trollstore"] && !isTSUpdate) || [immutableAppBundleIdentifiers() containsObject:appId.lowercaseString]) NSString* originalAppId = appInfoDict[@"CFBundleIdentifier"];
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0) return 176;
NSSet* immutableBundleIds = immutableAppBundleIdentifiers();
BOOL isTrollStoreIdentifier = [originalAppId.lowercaseString isEqualToString:@"com.opa334.trollstore"] || [originalAppId.lowercaseString hasPrefix:@"com.opa334.trollstore."];
if((isTrollStoreIdentifier && !isTSUpdate) || [immutableBundleIds containsObject:originalAppId.lowercaseString])
{ {
return 179; return 179;
} }
if(!infoDictionaryForAppPath(appBundleToInstallPath)) return 172;
if(!isTSUpdate) if(!isTSUpdate)
{ {
applyPatchesToInfoDictionary(appBundleToInstallPath); NSString* existingStealthAppId = installedStealthAppIdForOriginalAppId(originalAppId);
BOOL hasStealthInstall = [existingStealthAppId isKindOfClass:NSString.class] && existingStealthAppId.length > 0;
BOOL hasNonStealthInstall = [LSApplicationProxy applicationProxyForIdentifier:originalAppId].installed;
// Disallow mixing install modes for the same original app identifier.
if((stealthInstall && hasNonStealthInstall) || (!stealthInstall && hasStealthInstall))
{
return 186;
}
}
if(!isTSUpdate || stealthInstall)
{
applyPatchesToInfoDictionary(appBundleToInstallPath, stealthInstall, stripURLSchemesInStealth);
}
NSString* appId = appIdForAppPath(appBundleToInstallPath);
if(!appId) return 176;
if([immutableBundleIds containsObject:appId.lowercaseString])
{
return 179;
} }
BOOL requiresDevMode = NO; BOOL requiresDevMode = NO;
@ -1190,7 +1264,7 @@ int uninstallAppById(NSString* appId, BOOL useCustomMethod)
// 167: IPA does not appear to contain an app // 167: IPA does not appear to contain an app
// 180: IPA's main binary is encrypted // 180: IPA's main binary is encrypted
// 184: IPA contains additional encrypted binaries // 184: IPA contains additional encrypted binaries
int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipUICache) int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipUICache, BOOL stealthInstall)
{ {
cleanRestrictions(); cleanRestrictions();
@ -1209,7 +1283,7 @@ int installIpa(NSString* ipaPath, BOOL force, BOOL useInstalldMethod, BOOL skipU
return 168; return 168;
} }
int ret = installApp(tmpPackagePath, YES, force, NO, useInstalldMethod, skipUICache); int ret = installApp(tmpPackagePath, YES, force, NO, useInstalldMethod, skipUICache, stealthInstall, YES);
[[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil];
@ -1261,6 +1335,9 @@ int installTrollStore(NSString* pathToTar)
NSString* tmpTrollStorePath = [tmpPayloadPath stringByAppendingPathComponent:@"TrollStore.app"]; NSString* tmpTrollStorePath = [tmpPayloadPath stringByAppendingPathComponent:@"TrollStore.app"];
if(![[NSFileManager defaultManager] fileExistsAtPath:tmpTrollStorePath]) return 1; if(![[NSFileManager defaultManager] fileExistsAtPath:tmpTrollStorePath]) return 1;
NSString* previousTrollStorePath = trollStorePath();
NSString* previousTrollStoreAppPath = trollStoreAppPath();
NSString* previousTrollStoreAppId = appIdForAppPath(previousTrollStoreAppPath);
//if (@available(iOS 16, *)) {} else { //if (@available(iOS 16, *)) {} else {
// Transfer existing ldid installation if it exists // Transfer existing ldid installation if it exists
@ -1312,8 +1389,21 @@ int installTrollStore(NSString* pathToTar)
_installPersistenceHelper(persistenceHelperApp, trollStorePersistenceHelper, trollStoreRootHelper); _installPersistenceHelper(persistenceHelperApp, trollStorePersistenceHelper, trollStoreRootHelper);
} }
int ret = installApp(tmpPackagePath, NO, YES, YES, YES, NO); int ret = installApp(tmpPackagePath, NO, YES, YES, YES, NO, YES, NO);
NSLog(@"[installTrollStore] installApp => %d", ret); NSLog(@"[installTrollStore] installApp => %d", ret);
NSString* targetTrollStoreAppId = (ret == 0) ? appIdForAppPath(tmpTrollStorePath) : nil;
if(ret == 0 && previousTrollStorePath && previousTrollStoreAppPath && previousTrollStoreAppId && targetTrollStoreAppId)
{
BOOL didChangeBundleId = ![previousTrollStoreAppId isEqualToString:targetTrollStoreAppId];
MCMAppContainer* targetContainer = [MCMAppContainer containerWithIdentifier:targetTrollStoreAppId createIfNecessary:NO existed:nil error:nil];
BOOL sameContainerPath = [previousTrollStorePath isEqualToString:targetContainer.url.path];
if(didChangeBundleId && !sameContainerPath)
{
// Migrate away from legacy non-stealth install by removing the old app container.
registerPath(previousTrollStoreAppPath, YES, YES);
[[NSFileManager defaultManager] removeItemAtPath:previousTrollStorePath error:nil];
}
}
[[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil]; [[NSFileManager defaultManager] removeItemAtPath:tmpPackagePath error:nil];
return ret; return ret;
} }
@ -1547,8 +1637,9 @@ int MAIN_NAME(int argc, char *argv[], char *envp[])
BOOL useInstalldMethod = [args containsObject:@"installd"]; BOOL useInstalldMethod = [args containsObject:@"installd"];
BOOL force = [args containsObject:@"force"]; BOOL force = [args containsObject:@"force"];
BOOL skipUICache = [args containsObject:@"skip-uicache"]; BOOL skipUICache = [args containsObject:@"skip-uicache"];
BOOL stealthInstall = [args containsObject:@"stealth"];
NSString* ipaPath = args.lastObject; NSString* ipaPath = args.lastObject;
ret = installIpa(ipaPath, force, useInstalldMethod, skipUICache); ret = installIpa(ipaPath, force, useInstalldMethod, skipUICache, stealthInstall);
} }
else if([cmd isEqualToString:@"uninstall"]) else if([cmd isEqualToString:@"uninstall"])
{ {

View File

@ -131,7 +131,8 @@ bool registerPath(NSString *path, BOOL unregister, BOOL forceSystem) {
dictToRegister[@"Container"] = containerPath; dictToRegister[@"Container"] = containerPath;
dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath, appContainerized); dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath, appContainerized);
} }
dictToRegister[@"IsDeletable"] = @(![appBundleID isEqualToString:@"com.opa334.TrollStore"] && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_15_0); BOOL isTrollStoreMainApp = [path isEqualToString:trollStoreAppPath()];
dictToRegister[@"IsDeletable"] = @(!isTrollStoreMainApp && kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_15_0);
dictToRegister[@"Path"] = path; dictToRegister[@"Path"] = path;
dictToRegister[@"SignerOrganization"] = @"Apple Inc."; dictToRegister[@"SignerOrganization"] = @"Apple Inc.";

View File

@ -39,6 +39,7 @@ extern NSArray* trollStoreInactiveInstalledAppBundlePaths(void);
extern NSArray* trollStoreInstalledAppContainerPaths(void); extern NSArray* trollStoreInstalledAppContainerPaths(void);
extern NSString* trollStorePath(void); extern NSString* trollStorePath(void);
extern NSString* trollStoreAppPath(void); extern NSString* trollStoreAppPath(void);
extern NSString* installedStealthAppIdForOriginalAppId(NSString* originalAppId);
extern BOOL isRemovableSystemApp(NSString* appId); extern BOOL isRemovableSystemApp(NSString* appId);

View File

@ -406,12 +406,64 @@ NSArray *trollStoreInactiveInstalledAppBundlePaths(void)
return trollStoreInstalledAppBundlePathsInternal(TS_INACTIVE_MARKER); return trollStoreInstalledAppBundlePathsInternal(TS_INACTIVE_MARKER);
} }
static NSString* trollStoreContainerPathForAppIdentifier(NSString* appId)
{
if(![appId isKindOfClass:NSString.class] || appId.length == 0) return nil;
MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:appId createIfNecessary:NO existed:NULL error:nil];
NSString* appContainerPath = appContainer.url.path;
if(!appContainerPath) return nil;
NSString* appPath = [appContainerPath stringByAppendingPathComponent:@"TrollStore.app"];
if(![[NSFileManager defaultManager] fileExistsAtPath:appPath]) return nil;
return appContainerPath;
}
NSString* installedStealthAppIdForOriginalAppId(NSString* originalAppId)
{
if(![originalAppId isKindOfClass:NSString.class] || originalAppId.length == 0) return nil;
LSEnumerator* enumerator = [LSEnumerator enumeratorForApplicationProxiesWithOptions:0];
LSApplicationProxy* appProxy;
while(appProxy = [enumerator nextObject])
{
if(!appProxy.installed) continue;
NSString* appPath = appProxy.bundleURL.path;
if(![appPath isKindOfClass:NSString.class] || appPath.length == 0) continue;
NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:[appPath stringByAppendingPathComponent:@"Info.plist"]];
NSString* installedOriginalAppId = infoDict[@"TSOriginalBundleIdentifier"];
NSString* installedAppId = infoDict[@"CFBundleIdentifier"];
if([installedOriginalAppId isKindOfClass:NSString.class] &&
[installedAppId isKindOfClass:NSString.class] &&
installedAppId.length > 0 &&
[installedOriginalAppId.lowercaseString isEqualToString:originalAppId.lowercaseString])
{
return installedAppId;
}
}
return nil;
}
NSString* trollStorePath() NSString* trollStorePath()
{ {
#ifndef TROLLSTORE_LITE
NSString* appContainerPath = trollStoreContainerPathForAppIdentifier(APP_ID);
if(appContainerPath) return appContainerPath;
appContainerPath = trollStoreContainerPathForAppIdentifier(@"com.opa334.trollstore");
if(appContainerPath) return appContainerPath;
NSString* stealthAppId = installedStealthAppIdForOriginalAppId(@"com.opa334.trollstore");
return trollStoreContainerPathForAppIdentifier(stealthAppId);
#else
NSError* mcmError; NSError* mcmError;
MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:APP_ID createIfNecessary:NO existed:NULL error:&mcmError]; MCMAppContainer* appContainer = [MCMAppContainer containerWithIdentifier:APP_ID createIfNecessary:NO existed:NULL error:&mcmError];
if(!appContainer) return nil; if(!appContainer) return nil;
return appContainer.url.path; return appContainer.url.path;
#endif
} }
NSString* trollStoreAppPath() NSString* trollStoreAppPath()

View File

@ -11,8 +11,7 @@
- (NSArray*)installedAppPaths; - (NSArray*)installedAppPaths;
- (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 stealth:(BOOL)stealth log:(NSString**)logOut;
- (int)installIpa:(NSString*)pathToIpa;
- (int)uninstallApp:(NSString*)appId; - (int)uninstallApp:(NSString*)appId;
- (int)uninstallAppByPath:(NSString*)path; - (int)uninstallAppByPath:(NSString*)path;
- (BOOL)openApplicationWithBundleID:(NSString *)appID; - (BOOL)openApplicationWithBundleID:(NSString *)appID;

View File

@ -91,13 +91,16 @@ extern NSUserDefaults* trollStoreUserDefaults();
break; break;
case 185: case 185:
errorDescription = @"Failed to sign the app. The CoreTrust bypass returned a non zero status code."; errorDescription = @"Failed to sign the app. The CoreTrust bypass returned a non zero status code.";
break;
case 186:
errorDescription = @"This app is already installed using the opposite install mode (Stealth vs Normal). Uninstall it first before switching modes.";
} }
NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}]; NSError* error = [NSError errorWithDomain:TrollStoreErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : errorDescription}];
return error; return error;
} }
- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force log:(NSString**)logOut - (int)installIpa:(NSString*)pathToIpa force:(BOOL)force stealth:(BOOL)stealth log:(NSString**)logOut
{ {
NSMutableArray* args = [NSMutableArray new]; NSMutableArray* args = [NSMutableArray new];
[args addObject:@"install"]; [args addObject:@"install"];
@ -105,6 +108,10 @@ extern NSUserDefaults* trollStoreUserDefaults();
{ {
[args addObject:@"force"]; [args addObject:@"force"];
} }
if(stealth)
{
[args addObject:@"stealth"];
}
NSNumber* installationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"installationMethod"]; NSNumber* installationMethodToUseNum = [trollStoreUserDefaults() objectForKey:@"installationMethod"];
int installationMethodToUse = installationMethodToUseNum ? installationMethodToUseNum.intValue : 1; int installationMethodToUse = installationMethodToUseNum ? installationMethodToUseNum.intValue : 1;
if(installationMethodToUse == 1) if(installationMethodToUse == 1)
@ -122,11 +129,6 @@ extern NSUserDefaults* trollStoreUserDefaults();
return ret; return ret;
} }
- (int)installIpa:(NSString*)pathToIpa
{
return [self installIpa:pathToIpa force:NO log:nil];
}
- (int)uninstallApp:(NSString*)appId - (int)uninstallApp:(NSString*)appId
{ {
if(!appId) return -200; if(!appId) return -200;

View File

@ -4,8 +4,7 @@
+ (void)presentInstallationAlertIfEnabledForFile:(NSString*)pathToIPA isRemoteInstall:(BOOL)remoteInstall completion:(void (^)(BOOL, NSError*))completionBlock; + (void)presentInstallationAlertIfEnabledForFile:(NSString*)pathToIPA isRemoteInstall:(BOOL)remoteInstall completion:(void (^)(BOOL, NSError*))completionBlock;
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completion; + (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force stealthInstall:(BOOL)stealth completion:(void (^)(BOOL, NSError*))completion;
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA completion:(void (^)(BOOL, NSError*))completion;
+ (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completion; + (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completion;

View File

@ -9,7 +9,7 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
@implementation TSInstallationController @implementation TSInstallationController
+ (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force completion:(void (^)(BOOL, NSError*))completionBlock + (void)handleAppInstallFromFile:(NSString*)pathToIPA forceInstall:(BOOL)force stealthInstall:(BOOL)stealth completion:(void (^)(BOOL, NSError*))completionBlock
{ {
dispatch_async(dispatch_get_main_queue(), ^ dispatch_async(dispatch_get_main_queue(), ^
{ {
@ -18,7 +18,7 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
{ {
// Install IPA // Install IPA
NSString* log; NSString* log;
int ret = [[TSApplicationsManager sharedInstance] installIpa:pathToIPA force:force log:&log]; int ret = [[TSApplicationsManager sharedInstance] installIpa:pathToIPA force:force stealth:stealth log:&log];
NSError* error; NSError* error;
if(ret != 0) if(ret != 0)
@ -46,7 +46,7 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) UIAlertAction* forceInstallAction = [UIAlertAction actionWithTitle:@"Force Installation" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action)
{ {
[self handleAppInstallFromFile:pathToIPA forceInstall:YES completion:completionBlock]; [self handleAppInstallFromFile:pathToIPA forceInstall:YES stealthInstall:stealth completion:completionBlock];
}]; }];
[errorAlert addAction:forceInstallAction]; [errorAlert addAction:forceInstallAction];
@ -120,7 +120,7 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
{ {
if(installAlertConfiguration == 2 || (installAlertConfiguration == 1 && !remoteInstall)) if(installAlertConfiguration == 2 || (installAlertConfiguration == 1 && !remoteInstall))
{ {
[self handleAppInstallFromFile:pathToIPA completion:completionBlock]; [self handleAppInstallFromFile:pathToIPA forceInstall:NO stealthInstall:NO completion:completionBlock];
return; return;
} }
} }
@ -137,10 +137,16 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
installAlert.attributedMessage = [appInfo detailedInfoDescription]; installAlert.attributedMessage = [appInfo detailedInfoDescription];
UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) UIAlertAction* installAction = [UIAlertAction actionWithTitle:@"Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action)
{ {
[self handleAppInstallFromFile:pathToIPA completion:completionBlock]; [self handleAppInstallFromFile:pathToIPA forceInstall:NO stealthInstall:NO completion:completionBlock];
}]; }];
[installAlert addAction:installAction]; [installAlert addAction:installAction];
UIAlertAction* stealthInstallAction = [UIAlertAction actionWithTitle:@"Stealth Install" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action)
{
[self handleAppInstallFromFile:pathToIPA forceInstall:NO stealthInstall:YES completion:completionBlock];
}];
[installAlert addAction:stealthInstallAction];
UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action)
{ {
if(completionBlock) completionBlock(NO, nil); if(completionBlock) completionBlock(NO, nil);
@ -161,11 +167,6 @@ extern NSUserDefaults* trollStoreUserDefaults(void);
}]; }];
} }
+ (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 + (void)handleAppInstallFromRemoteURL:(NSURL*)remoteURL completion:(void (^)(BOOL, NSError*))completionBlock
{ {
NSURLRequest* downloadRequest = [NSURLRequest requestWithURL:remoteURL]; NSURLRequest* downloadRequest = [NSURLRequest requestWithURL:remoteURL];