From f1fb843e27c5b90952c28c1324bfcd9daabaee0c Mon Sep 17 00:00:00 2001 From: Nebula Date: Sat, 3 Sep 2022 10:49:53 -0400 Subject: [PATCH 1/8] Update main.m --- Helper/main.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/main.m b/Helper/main.m index 2daa446..f3af391 100644 --- a/Helper/main.m +++ b/Helper/main.m @@ -229,7 +229,7 @@ BOOL signApp(NSString* appPath, NSError** error) else { // app has entitlements, keep them - ldidRet = runLdid(@[@"-S", @"-M", certArg, appPath], nil, &errorOutput); + ldidRet = runLdid(@[@"-s", certArg, appPath], nil, &errorOutput); } NSLog(@"ldid exited with status %d", ldidRet); From 068f735233ad372fd74747b782c8ba1b9dc26f1b Mon Sep 17 00:00:00 2001 From: opa334 Date: Sat, 3 Sep 2022 18:49:43 +0200 Subject: [PATCH 2/8] 1.0.2 --- _compile/build_full.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_compile/build_full.sh b/_compile/build_full.sh index 1ece343..2da9433 100755 --- a/_compile/build_full.sh +++ b/_compile/build_full.sh @@ -36,6 +36,8 @@ make clean make package FINALPACKAGE=1 cd - +rm ../PersistenceHelper/Resources/trollstorehelper + cp ../PersistenceHelper/.theos/obj/TrollStorePersistenceHelper.app/TrollStorePersistenceHelper ./out/TrollStore.app/PersistenceHelper ldid -S -M -Kcert.p12 ./out/TrollStore.app/PersistenceHelper From c2a5e5b9884890f7b016628b280aac937ac1e2ff Mon Sep 17 00:00:00 2001 From: opa334 Date: Sat, 3 Sep 2022 18:49:53 +0200 Subject: [PATCH 3/8] 1.0.2 --- Helper/control | 2 +- Helper/main.m | 51 ++- Helper/uicache.m | 306 +++++++++++++----- .../TrollInstaller/exploit/IOGPU.c | 3 + PersistenceHelper/Resources/Info.plist | 2 +- PersistenceHelper/Resources/trollstorehelper | Bin 122016 -> 0 bytes PersistenceHelper/control | 2 +- Store/Resources/Info.plist | 2 +- Store/TSApplicationsManager.h | 1 + Store/TSApplicationsManager.m | 19 +- Store/TSSceneDelegate.m | 94 ++++-- Store/control | 2 +- 12 files changed, 353 insertions(+), 131 deletions(-) delete mode 100755 PersistenceHelper/Resources/trollstorehelper diff --git a/Helper/control b/Helper/control index ad89a46..1c45e31 100644 --- a/Helper/control +++ b/Helper/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstoreroothelper Name: trollstoreroothelper -Version: 1.0.1 +Version: 1.0.2 Architecture: iphoneos-arm Description: An awesome tool of some sort!! Maintainer: opa334 diff --git a/Helper/main.m b/Helper/main.m index 2daa446..924261f 100644 --- a/Helper/main.m +++ b/Helper/main.m @@ -200,6 +200,16 @@ NSString* dumpEntitlements(NSString* binaryPath) return output; } +NSDictionary* dumpEntitlementsDict(NSString* binaryPath) +{ + NSString* entitlementsString = dumpEntitlements(binaryPath); + NSData* plistData = [entitlementsString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSPropertyListFormat format; + NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:&format error:&error]; + return plist; +} + BOOL signApp(NSString* appPath, NSError** error) { if(!isLdidInstalled()) return NO; @@ -246,8 +256,10 @@ BOOL signApp(NSString* appPath, NSError** error) // 170: failed to create container for app bundle // 171: a non trollstore app with the same identifier is already installled // 172: no info.plist found in app -int installApp(NSString* appPath, BOOL sign, NSError** error) +int installApp(NSString* appPath, BOOL sign, BOOL force, NSError** error) { + NSLog(@"[installApp force = %d]", force); + NSString* appId = appIdForAppPath(appPath); if(!appId) return 172; @@ -281,12 +293,15 @@ int installApp(NSString* appPath, BOOL sign, NSError** error) // Make sure there isn't already an app store app installed with the same identifier NSURL* trollStoreMarkURL = [appContainer.url URLByAppendingPathComponent:@"_TrollStore"]; - if(existed && !isEmpty && ![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil]) + if(existed && !isEmpty && ![trollStoreMarkURL checkResourceIsReachableAndReturnError:nil] && !force) { NSLog(@"[installApp] already installed and not a TrollStore app... bailing out"); return 171; } + // Mark app as TrollStore app + [[NSFileManager defaultManager] createFileAtPath:trollStoreMarkURL.path contents:[NSData data] attributes:nil]; + // Apply correct permissions NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:appPath] includingPropertiesForKeys:nil options:0 errorHandler:nil]; NSURL* fileURL; @@ -342,7 +357,7 @@ int installApp(NSString* appPath, BOOL sign, NSError** error) while(fileURL = [enumerator nextObject]) { // do not under any circumstance delete this file as it makes iOS loose the app registration - if([fileURL.lastPathComponent isEqualToString:@".com.apple.mobile_container_manager.metadata.plist"]) + if([fileURL.lastPathComponent isEqualToString:@".com.apple.mobile_container_manager.metadata.plist"] || [fileURL.lastPathComponent isEqualToString:@"_TrollStore"]) { NSLog(@"[installApp] skip removal of %@", fileURL); continue; @@ -359,9 +374,6 @@ int installApp(NSString* appPath, BOOL sign, NSError** error) BOOL suc = [[NSFileManager defaultManager] copyItemAtPath:appPath toPath:newAppPath error:error]; if(suc) { - // Mark app as TrollStore app - [[NSFileManager defaultManager] createFileAtPath:trollStoreMarkURL.path contents:[NSData data] attributes:nil]; - NSLog(@"[installApp] app installed, adding to icon cache now..."); registerPath((char*)newAppPath.UTF8String, 0); return 0; @@ -382,6 +394,7 @@ int uninstallApp(NSString* appId, NSError** error) MCMContainer *appContainer = [objc_getClass("MCMAppDataContainer") containerWithIdentifier:appId createIfNecessary:NO existed:nil error:nil]; + NSLog(@"1"); NSString *containerPath = [appContainer url].path; if(containerPath) { @@ -391,11 +404,11 @@ int uninstallApp(NSString* appId, NSError** error) } // delete group container paths - for(NSURL* groupURL in [appProxy groupContainerURLs]) + [[appProxy groupContainerURLs] enumerateKeysAndObjectsUsingBlock:^(NSString* groupID, NSURL* groupURL, BOOL* stop) { - [[NSFileManager defaultManager] removeItemAtPath:groupURL.path error:error]; - NSLog(@"deleting %@", groupURL.path); - } + [[NSFileManager defaultManager] removeItemAtURL:groupURL error:nil]; + NSLog(@"deleting %@", groupURL); + }]; // delete app plugin paths for(LSPlugInKitProxy* pluginProxy in appProxy.plugInKitPlugins) @@ -427,7 +440,7 @@ int uninstallApp(NSString* appId, NSError** error) // 166: IPA does not exist or is not accessible // 167: IPA does not appear to contain an app -int installIpa(NSString* ipaPath, NSError** error) +int installIpa(NSString* ipaPath, BOOL force, NSError** error) { if(![[NSFileManager defaultManager] fileExistsAtPath:ipaPath]) return 166; @@ -455,7 +468,7 @@ int installIpa(NSString* ipaPath, NSError** error) } if(!tmpAppPath) return 167; - int ret = installApp(tmpAppPath, YES, error); + int ret = installApp(tmpAppPath, YES, force, error); [[NSFileManager defaultManager] removeItemAtPath:tmpAppPath error:nil]; @@ -530,7 +543,7 @@ BOOL installTrollStore(NSString* pathToTar) _installPersistenceHelper(persistenceHelperApp, trollStorePersistenceHelper, trollStoreRootHelper); } - return installApp(tmpTrollStore, NO, nil);; + return installApp(tmpTrollStore, NO, YES, nil);; } void refreshAppRegistrations() @@ -663,9 +676,19 @@ int main(int argc, char *argv[], char *envp[]) { NSString* cmd = [NSString stringWithUTF8String:argv[1]]; if([cmd isEqualToString:@"install"]) { + NSLog(@"argc = %d", argc); + BOOL force = NO; if(argc <= 2) return -3; + if(argc > 3) + { + NSLog(@"argv3 = %s", argv[3]); + if(!strcmp(argv[3], "force")) + { + force = YES; + } + } NSString* ipaPath = [NSString stringWithUTF8String:argv[2]]; - ret = installIpa(ipaPath, &error); + ret = installIpa(ipaPath, force, &error); } else if([cmd isEqualToString:@"uninstall"]) { if(argc <= 2) return -3; diff --git a/Helper/uicache.m b/Helper/uicache.m index f5fbb27..7199274 100644 --- a/Helper/uicache.m +++ b/Helper/uicache.m @@ -4,89 +4,247 @@ #import #import "dlfcn.h" -void registerPath(char *path, int unregister) +// uicache on steroids + +extern NSDictionary* dumpEntitlementsDict(NSString* binaryPath); + +NSDictionary* constructGroupsContainersForEntitlements(NSDictionary* entitlements, BOOL systemGroups) { - if(!path) return; + if(!entitlements) return nil; - LSApplicationWorkspace *workspace = - [LSApplicationWorkspace defaultWorkspace]; - if (unregister && ![[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithUTF8String:path]]) { - LSApplicationProxy *app = [LSApplicationProxy - applicationProxyForIdentifier:[NSString stringWithUTF8String:path]]; - if (app.bundleURL) - path = (char *)[[app bundleURL] fileSystemRepresentation]; - } + NSString* entitlementForGroups; + NSString* mcmClass; + if(systemGroups) + { + entitlementForGroups = @"com.apple.security.system-groups"; + mcmClass = @"MCMSystemDataContainer"; + } + else + { + entitlementForGroups = @"com.apple.security.application-groups"; + mcmClass = @"MCMSharedDataContainer"; + } - NSString *rawPath = [NSString stringWithUTF8String:path]; - rawPath = [rawPath stringByResolvingSymlinksInPath]; + NSArray* groupIDs = entitlements[entitlementForGroups]; + if(groupIDs && [groupIDs isKindOfClass:[NSArray class]]) + { + NSMutableDictionary* groupContainers = [NSMutableDictionary new]; - NSDictionary *infoPlist = [NSDictionary - dictionaryWithContentsOfFile: - [rawPath stringByAppendingPathComponent:@"Info.plist"]]; - NSString *bundleID = [infoPlist objectForKey:@"CFBundleIdentifier"]; + for(NSString* groupID in groupIDs) + { + MCMContainer* container = [NSClassFromString(mcmClass) containerWithIdentifier:groupID createIfNecessary:YES existed:nil error:nil]; + if(container.url) + { + groupContainers[groupID] = container.url.path; + } + } - NSURL *url = [NSURL fileURLWithPath:rawPath]; + return groupContainers.copy; + } - if (bundleID && !unregister) { - MCMContainer *appContainer = [objc_getClass("MCMAppDataContainer") - containerWithIdentifier:bundleID - createIfNecessary:YES - existed:nil - error:nil]; - NSString *containerPath = [appContainer url].path; + return nil; +} - NSMutableDictionary *plist = [NSMutableDictionary dictionary]; - [plist setObject:@"System" forKey:@"ApplicationType"]; - [plist setObject:@1 forKey:@"BundleNameIsLocalized"]; - [plist setObject:bundleID forKey:@"CFBundleIdentifier"]; - [plist setObject:@0 forKey:@"CompatibilityState"]; - if (containerPath) [plist setObject:containerPath forKey:@"Container"]; - [plist setObject:@0 forKey:@"IsDeletable"]; - [plist setObject:rawPath forKey:@"Path"]; +BOOL constructContainerizationForEntitlements(NSDictionary* entitlements) +{ + NSNumber* noContainer = entitlements[@"com.apple.private.security.no-container"]; + if(noContainer && [noContainer isKindOfClass:[NSNumber class]]) + { + if(noContainer.boolValue) + { + return NO; + } + } - NSString *pluginsPath = - [rawPath stringByAppendingPathComponent:@"PlugIns"]; - NSArray *plugins = [[NSFileManager defaultManager] - contentsOfDirectoryAtPath:pluginsPath - error:nil]; + NSNumber* containerRequired = entitlements[@"com.apple.private.security.container-required"]; + if(containerRequired && [containerRequired isKindOfClass:[NSNumber class]]) + { + if(!containerRequired.boolValue) + { + return NO; + } + } - NSMutableDictionary *bundlePlugins = [NSMutableDictionary dictionary]; - for (NSString *pluginName in plugins) { - NSString *fullPath = - [pluginsPath stringByAppendingPathComponent:pluginName]; + return YES; +} - NSDictionary *infoPlist = [NSDictionary - dictionaryWithContentsOfFile: - [fullPath stringByAppendingPathComponent:@"Info.plist"]]; - NSString *pluginBundleID = - [infoPlist objectForKey:@"CFBundleIdentifier"]; - if (!pluginBundleID) continue; - MCMContainer *pluginContainer = - [objc_getClass("MCMPluginKitPluginDataContainer") - containerWithIdentifier:pluginBundleID - createIfNecessary:YES - existed:nil - error:nil]; - NSString *pluginContainerPath = [pluginContainer url].path; +NSString* constructTeamIdentifierForEntitlements(NSDictionary* entitlements) +{ + NSString* teamIdentifier = entitlements[@"com.apple.developer.team-identifier"]; + if(teamIdentifier && [teamIdentifier isKindOfClass:[NSString class]]) + { + return teamIdentifier; + } + return nil; +} - NSMutableDictionary *pluginPlist = [NSMutableDictionary dictionary]; - [pluginPlist setObject:@"PluginKitPlugin" - forKey:@"ApplicationType"]; - [pluginPlist setObject:@1 forKey:@"BundleNameIsLocalized"]; - [pluginPlist setObject:pluginBundleID forKey:@"CFBundleIdentifier"]; - [pluginPlist setObject:@0 forKey:@"CompatibilityState"]; - [pluginPlist setObject:pluginContainerPath forKey:@"Container"]; - [pluginPlist setObject:fullPath forKey:@"Path"]; - [pluginPlist setObject:bundleID forKey:@"PluginOwnerBundleID"]; - [bundlePlugins setObject:pluginPlist forKey:pluginBundleID]; - } - [plist setObject:bundlePlugins forKey:@"_LSBundlePlugins"]; - if (![workspace registerApplicationDictionary:plist]) { - fprintf(stderr, "Error: Unable to register %s\n", path); - } - } else { - if (![workspace unregisterApplication:url]) { - fprintf(stderr, "Error: Unable to unregister %s\n", path); - } - } +NSDictionary* constructEnvironmentVariablesForContainerPath(NSString* containerPath) +{ + NSString* tmpDir = [containerPath stringByAppendingPathComponent:@"tmp"]; + return @{ + @"CFFIXED_USER_HOME" : containerPath, + @"HOME" : containerPath, + @"TMPDIR" : tmpDir + }; +} + +void registerPath(char* cPath, int unregister) +{ + if(!cPath) return; + NSString* path = [NSString stringWithUTF8String:cPath]; + + LSApplicationWorkspace* workspace = [LSApplicationWorkspace defaultWorkspace]; + if(unregister && ![[NSFileManager defaultManager] fileExistsAtPath:path]) + { + LSApplicationProxy* app = [LSApplicationProxy applicationProxyForIdentifier:path]; + if(app.bundleURL) + { + path = [app bundleURL].path; + } + } + + path = [path stringByResolvingSymlinksInPath]; + + NSDictionary* appInfoPlist = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Info.plist"]]; + NSString* appBundleID = [appInfoPlist objectForKey:@"CFBundleIdentifier"]; + + if(appBundleID && !unregister) + { + MCMContainer* appContainer = [NSClassFromString(@"MCMAppDataContainer") containerWithIdentifier:appBundleID createIfNecessary:YES existed:nil error:nil]; + NSString* containerPath = [appContainer url].path; + + NSMutableDictionary* dictToRegister = [NSMutableDictionary dictionary]; + + // Add entitlements + + NSString* appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]]; + NSDictionary* entitlements = dumpEntitlementsDict(appExecutablePath); + if(entitlements) + { + dictToRegister[@"Entitlements"] = entitlements; + } + + // Misc + + dictToRegister[@"ApplicationType"] = @"System"; + dictToRegister[@"BundleNameIsLocalized"] = @1; + dictToRegister[@"CFBundleIdentifier"] = appBundleID; + dictToRegister[@"CodeInfoIdentifier"] = appBundleID; + dictToRegister[@"CompatibilityState"] = @0; + if(containerPath) + { + dictToRegister[@"Container"] = containerPath; + dictToRegister[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(containerPath); + } + dictToRegister[@"IsDeletable"] = @0; + dictToRegister[@"Path"] = path; + dictToRegister[@"IsContainerized"] = @(constructContainerizationForEntitlements(entitlements)); + + NSString* teamIdentifier = constructTeamIdentifierForEntitlements(entitlements); + if(teamIdentifier) dictToRegister[@"TeamIdentifier"] = teamIdentifier; + + // Add group containers + + NSDictionary* appGroupContainers = constructGroupsContainersForEntitlements(entitlements, NO); + NSDictionary* systemGroupContainers = constructGroupsContainersForEntitlements(entitlements, NO); + NSMutableDictionary* groupContainers = [NSMutableDictionary new]; + [groupContainers addEntriesFromDictionary:appGroupContainers]; + [groupContainers addEntriesFromDictionary:systemGroupContainers]; + if(groupContainers.count) + { + if(appGroupContainers.count) + { + dictToRegister[@"HasAppGroupContainers"] = @YES; + } + if(systemGroupContainers.count) + { + dictToRegister[@"HasSystemGroupContainers"] = @YES; + } + dictToRegister[@"GroupContainers"] = groupContainers.copy; + } + + // Add plugins + + NSString* pluginsPath = [path stringByAppendingPathComponent:@"PlugIns"]; + NSArray* plugins = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:pluginsPath error:nil]; + + NSMutableDictionary* bundlePlugins = [NSMutableDictionary dictionary]; + for (NSString* pluginName in plugins) + { + NSString* pluginPath = [pluginsPath stringByAppendingPathComponent:pluginName]; + + NSDictionary* pluginInfoPlist = [NSDictionary dictionaryWithContentsOfFile:[pluginPath stringByAppendingPathComponent:@"Info.plist"]]; + NSString* pluginBundleID = [pluginInfoPlist objectForKey:@"CFBundleIdentifier"]; + + if(!pluginBundleID) continue; + MCMContainer* pluginContainer = [NSClassFromString(@"MCMPluginKitPluginDataContainer") containerWithIdentifier:pluginBundleID createIfNecessary:YES existed:nil error:nil]; + NSString* pluginContainerPath = [pluginContainer url].path; + + NSMutableDictionary* pluginDict = [NSMutableDictionary dictionary]; + + // Add entitlements + + NSString* pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]]; + NSDictionary* pluginEntitlements = dumpEntitlementsDict(pluginExecutablePath); + if(pluginEntitlements) + { + pluginDict[@"Entitlements"] = pluginEntitlements; + } + + // Misc + + pluginDict[@"ApplicationType"] = @"PluginKitPlugin"; + pluginDict[@"BundleNameIsLocalized"] = @1; + pluginDict[@"CFBundleIdentifier"] = pluginBundleID; + pluginDict[@"CodeInfoIdentifier"] = pluginBundleID; + pluginDict[@"CompatibilityState"] = @0; + if(pluginContainerPath) + { + pluginDict[@"Container"] = pluginContainerPath; + pluginDict[@"EnvironmentVariables"] = constructEnvironmentVariablesForContainerPath(pluginContainerPath); + } + pluginDict[@"Path"] = pluginPath; + pluginDict[@"PluginOwnerBundleID"] = appBundleID; + pluginDict[@"IsContainerized"] = @(constructContainerizationForEntitlements(pluginEntitlements)); + + NSString* pluginTeamIdentifier = constructTeamIdentifierForEntitlements(pluginEntitlements); + if(pluginTeamIdentifier) pluginDict[@"TeamIdentifier"] = pluginTeamIdentifier; + + // Add plugin group containers + + NSDictionary* pluginAppGroupContainers = constructGroupsContainersForEntitlements(pluginEntitlements, NO); + NSDictionary* pluginSystemGroupContainers = constructGroupsContainersForEntitlements(pluginEntitlements, NO); + NSMutableDictionary* pluginGroupContainers = [NSMutableDictionary new]; + [pluginGroupContainers addEntriesFromDictionary:pluginAppGroupContainers]; + [pluginGroupContainers addEntriesFromDictionary:pluginSystemGroupContainers]; + if(pluginGroupContainers.count) + { + if(pluginAppGroupContainers.count) + { + pluginDict[@"HasAppGroupContainers"] = @YES; + } + if(pluginSystemGroupContainers.count) + { + pluginDict[@"HasSystemGroupContainers"] = @YES; + } + pluginDict[@"GroupContainers"] = pluginGroupContainers.copy; + } + + [bundlePlugins setObject:pluginDict forKey:pluginBundleID]; + } + [dictToRegister setObject:bundlePlugins forKey:@"_LSBundlePlugins"]; + + if(![workspace registerApplicationDictionary:dictToRegister]) + { + NSLog(@"Error: Unable to register %@", path); + } + } + else + { + NSURL* url = [NSURL fileURLWithPath:path]; + if(![workspace unregisterApplication:url]) + { + NSLog(@"Error: Unable to unregister %@", path); + } + } } \ No newline at end of file diff --git a/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c b/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c index d858d94..1dc0c93 100644 --- a/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c +++ b/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c @@ -65,6 +65,7 @@ int IOGPU_get_command_queue_extra_refills_needed(void) || strstr(u.machine, "iPhone14,") || strstr(u.machine, "iPad13,") || strstr(u.machine, "iPad7,") + || strstr(u.machine, "iPad12,") ) { return 1; @@ -72,10 +73,12 @@ int IOGPU_get_command_queue_extra_refills_needed(void) // iPhone 8, X // iPhone XS, XR // iPad Pro A12Z + // iPad 8 A12 else if ( strstr(u.machine, "iPhone10,") || strstr(u.machine, "iPhone11,") || strstr(u.machine, "iPad8,") + || strstr(u.machine, "iPad11,") ) { return 3; diff --git a/PersistenceHelper/Resources/Info.plist b/PersistenceHelper/Resources/Info.plist index d5c96d0..ca48dbd 100644 --- a/PersistenceHelper/Resources/Info.plist +++ b/PersistenceHelper/Resources/Info.plist @@ -52,7 +52,7 @@ iPhoneOS CFBundleVersion - 1.0 + 1.0.2 LSRequiresIPhoneOS UIDeviceFamily diff --git a/PersistenceHelper/Resources/trollstorehelper b/PersistenceHelper/Resources/trollstorehelper deleted file mode 100755 index 7d5533ed99e6ed51ddda78c54a8dfe323bc88434..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122016 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 Z5D*X$5D*X$5D*X$5D*X$5D@>z`~uE>)O!E` diff --git a/PersistenceHelper/control b/PersistenceHelper/control index 618e6b7..e805279 100644 --- a/PersistenceHelper/control +++ b/PersistenceHelper/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstorehelper Name: TrollStore Helper -Version: 1.0 +Version: 1.0.2 Architecture: iphoneos-arm Description: Helper app to install and manage TrollStore! Maintainer: opa334 diff --git a/Store/Resources/Info.plist b/Store/Resources/Info.plist index 16d3172..639658b 100644 --- a/Store/Resources/Info.plist +++ b/Store/Resources/Info.plist @@ -50,7 +50,7 @@ iPhoneOS CFBundleVersion - 1.0.1 + 1.0.2 LSRequiresIPhoneOS UIDeviceFamily diff --git a/Store/TSApplicationsManager.h b/Store/TSApplicationsManager.h index 2d4220f..2b238f7 100644 --- a/Store/TSApplicationsManager.h +++ b/Store/TSApplicationsManager.h @@ -14,6 +14,7 @@ - (NSString*)displayNameForAppPath:(NSString*)appPath; - (NSError*)errorForCode:(int)code; +- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force; - (int)installIpa:(NSString*)pathToIpa; - (int)uninstallApp:(NSString*)appId; diff --git a/Store/TSApplicationsManager.m b/Store/TSApplicationsManager.m index 979ca7d..957a3eb 100644 --- a/Store/TSApplicationsManager.m +++ b/Store/TSApplicationsManager.m @@ -67,7 +67,7 @@ errorDescription = @"Failed to create container for app bundle."; break; case 171: - errorDescription = @"A non-TrollStore app with the same identifier is already installed. If you are absolutely sure it is not, try refreshing icon cache in TrollStore settings or try rebooting your device."; + errorDescription = @"A non-TrollStore app with the same identifier is already installed. If you are absolutely sure it is not, you can force install it."; break; case 172: errorDescription = @"The app does not seem to contain an Info.plist"; @@ -78,13 +78,26 @@ return error; } -- (int)installIpa:(NSString*)pathToIpa +- (int)installIpa:(NSString*)pathToIpa force:(BOOL)force { - int ret = spawnRoot(helperPath(), @[@"install", pathToIpa]); + int ret; + if(force) + { + ret = spawnRoot(helperPath(), @[@"install", pathToIpa, @"force"]); + } + else + { + ret = spawnRoot(helperPath(), @[@"install", pathToIpa]); + } [[NSNotificationCenter defaultCenter] postNotificationName:@"ApplicationsChanged" object:nil]; return ret; } +- (int)installIpa:(NSString*)pathToIpa +{ + return [self installIpa:pathToIpa force:NO]; +} + - (int)uninstallApp:(NSString*)appId { int ret = spawnRoot(helperPath(), @[@"uninstall", appId]); diff --git a/Store/TSSceneDelegate.m b/Store/TSSceneDelegate.m index ae0db64..55b1a1d 100644 --- a/Store/TSSceneDelegate.m +++ b/Store/TSSceneDelegate.m @@ -5,7 +5,7 @@ @implementation TSSceneDelegate -- (void)handleURLContexts:(NSSet *)URLContexts scene:(UIWindowScene*)scene +- (void)doIPAInstall:(NSString*)ipaPath scene:(UIWindowScene*)scene force:(BOOL)force completion:(void (^)(void))completion { UIWindow* keyWindow = nil; for(UIWindow* window in scene.windows) @@ -17,6 +17,60 @@ } } + 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 + int ret = [[TSApplicationsManager sharedInstance] installIpa:ipaPath force:force]; + NSError* 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(); + } + }]; + 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]; + } + [errorAlert addAction:closeAction]; + + [keyWindow.rootViewController presentViewController:errorAlert animated:YES completion:nil]; + } + + if(ret != 171) + { + completion(); + } + }]; + }); + }); +} + +- (void)handleURLContexts:(NSSet *)URLContexts scene:(UIWindowScene*)scene +{ for(UIOpenURLContext* context in URLContexts) { NSLog(@"openURLContexts %@", context.URL); @@ -39,43 +93,13 @@ respring(); exit(0); } - }; + }; if ([url.pathExtension isEqualToString:@"ipa"]) { - 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 - int ret = [[TSApplicationsManager sharedInstance] installIpa:tmpCopyURL.path]; - NSError* 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:nil]; - [errorAlert addAction:closeAction]; - - [keyWindow.rootViewController presentViewController:errorAlert animated:YES completion:nil]; - } - - doneBlock(NO); - }]; - }); - }); + [self doIPAInstall:url.path scene:(UIWindowScene*)scene force:NO completion:^{ + doneBlock(NO); + }]; } else if([url.pathExtension isEqualToString:@"tar"]) { diff --git a/Store/control b/Store/control index 0238953..b69c4ba 100644 --- a/Store/control +++ b/Store/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstore Name: TrollStore -Version: 1.0.1 +Version: 1.0.2 Architecture: iphoneos-arm Description: An awesome application! Maintainer: opa334 From 771fbb6408307053d6914669927f1c29ac35d248 Mon Sep 17 00:00:00 2001 From: opa334 Date: Sat, 3 Sep 2022 18:50:29 +0200 Subject: [PATCH 4/8] 1.0.2 --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 13a4c0d..d8cbdf5 100644 --- a/LICENSE +++ b/LICENSE @@ -10,6 +10,7 @@ License: MIT Files: Helper/uicache.m Copyright: Copyright (c) 2019 CoolStar, Modified work Copyright (c) 2020-2022 Procursus Team + Modified work Copyright (c) 2022 Lars Fröder License: BSD-4-Clause Files: Installer/TrollInstaller/exploit From e2fd9515ad7457bd16b443c939b936d9bfbdea7c Mon Sep 17 00:00:00 2001 From: opa334 Date: Sun, 4 Sep 2022 00:48:40 +0200 Subject: [PATCH 5/8] 1.0.3 --- Helper/main.m | 178 +++++++++++++++--- Helper/uicache.m | 6 +- .../TrollInstaller/ViewController.m | 2 + PersistenceHelper/TSPHRootViewController.m | 29 +++ _compile/build_full.sh | 16 ++ 5 files changed, 198 insertions(+), 33 deletions(-) diff --git a/Helper/main.m b/Helper/main.m index 924261f..31b0594 100644 --- a/Helper/main.m +++ b/Helper/main.m @@ -8,9 +8,54 @@ #import #import "CoreServices.h" #import "Shared.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import #import +#ifdef __LP64__ +#define segment_command_universal segment_command_64 +#define mach_header_universal mach_header_64 +#define MH_MAGIC_UNIVERSAL MH_MAGIC_64 +#define MH_CIGAM_UNIVERSAL MH_CIGAM_64 +#else +#define segment_command_universal segment_command +#define mach_header_universal mach_header +#define MH_MAGIC_UNIVERSAL MH_MAGIC +#define MH_CIGAM_UNIVERSAL MH_CIGAM +#endif + +#define SWAP32(x) ((((x) & 0xff000000) >> 24) | (((x) & 0xff0000) >> 8) | (((x) & 0xff00) << 8) | (((x) & 0xff) << 24)) +uint32_t s32(uint32_t toSwap, BOOL shouldSwap) +{ + return shouldSwap ? SWAP32(toSwap) : toSwap; +} + +#define CPU_SUBTYPE_ARM64E_NEW_ABI 0x80000002 + +struct CSSuperBlob { + uint32_t magic; + uint32_t length; + uint32_t count; +}; + +struct CSBlob { + uint32_t type; + uint32_t offset; +}; + +#define CS_MAGIC_EMBEDDED_SIGNATURE 0xfade0cc0 +#define CS_MAGIC_EMBEDDED_SIGNATURE_REVERSED 0xc00cdefa +#define CS_MAGIC_EMBEDDED_ENTITLEMENTS 0xfade7171 + + extern mach_msg_return_t SBReloadIconForIdentifier(mach_port_t machport, const char* identifier); @interface SBSHomeScreenService : NSObject - (void)reloadIcons; @@ -176,38 +221,111 @@ int runLdid(NSArray* args, NSString** output, NSString** errorOutput) return WEXITSTATUS(status); } -NSString* dumpEntitlements(NSString* binaryPath) +NSDictionary* dumpEntitlements(NSString* binaryPath) { - NSString* output; - NSString* errorOutput; + char* entitlementsData = NULL; + uint32_t entitlementsLength = 0; - int ldidRet = runLdid(@[@"-e", binaryPath], &output, &errorOutput); + FILE* machoFile = fopen(binaryPath.UTF8String, "rb"); + struct mach_header_universal header; + fread(&header,sizeof(header),1,machoFile); - NSLog(@"entitlements dump exited with status %d", ldidRet); + if(header.magic == FAT_MAGIC || header.magic == FAT_CIGAM) + { + fseek(machoFile,0,SEEK_SET); + + struct fat_header fatHeader; + fread(&fatHeader,sizeof(fatHeader),1,machoFile); + + BOOL swpFat = fatHeader.magic == FAT_CIGAM; + + for(int i = 0; i < s32(fatHeader.nfat_arch, swpFat); i++) + { + struct fat_arch fatArch; + fseek(machoFile,sizeof(fatHeader) + sizeof(fatArch) * i,SEEK_SET); + fread(&fatArch,sizeof(fatArch),1,machoFile); + + if(s32(fatArch.cputype, swpFat) != CPU_TYPE_ARM64) + { + continue; + } + + fseek(machoFile,s32(fatArch.offset, swpFat),SEEK_SET); + struct mach_header_universal header; + fread(&header,sizeof(header),1,machoFile); + + BOOL swp = header.magic == MH_CIGAM_UNIVERSAL; + + // This code is cursed, don't stare at it too long or it will stare back at you + uint32_t offset = s32(fatArch.offset, swpFat) + sizeof(header); + for(int c = 0; c < s32(header.ncmds, swp); c++) + { + fseek(machoFile,offset,SEEK_SET); + struct load_command cmd; + fread(&cmd,sizeof(cmd),1,machoFile); + uint32_t normalizedCmd = s32(cmd.cmd,swp); + if(normalizedCmd == LC_CODE_SIGNATURE) + { + struct linkedit_data_command codeSignCommand; + fseek(machoFile,offset,SEEK_SET); + fread(&codeSignCommand,sizeof(codeSignCommand),1,machoFile); + uint32_t codeSignCmdOffset = s32(fatArch.offset, swpFat) + s32(codeSignCommand.dataoff, swp); + fseek(machoFile, codeSignCmdOffset, SEEK_SET); + struct CSSuperBlob superBlob; + fread(&superBlob, sizeof(superBlob), 1, machoFile); + if(SWAP32(superBlob.magic) == CS_MAGIC_EMBEDDED_SIGNATURE) + { + uint32_t itemCount = SWAP32(superBlob.count); + for(int i = 0; i < itemCount; i++) + { + fseek(machoFile, codeSignCmdOffset + sizeof(superBlob) + i * sizeof(struct CSBlob),SEEK_SET); + struct CSBlob blob; + fread(&blob, sizeof(struct CSBlob), 1, machoFile); + fseek(machoFile, codeSignCmdOffset + SWAP32(blob.offset),SEEK_SET); + uint32_t blobMagic; + fread(&blobMagic, sizeof(uint32_t), 1, machoFile); + if(SWAP32(blobMagic) == CS_MAGIC_EMBEDDED_ENTITLEMENTS) + { + uint32_t entitlementsLengthTmp; + fread(&entitlementsLengthTmp, sizeof(uint32_t), 1, machoFile); + entitlementsLength = SWAP32(entitlementsLengthTmp); + entitlementsData = malloc(entitlementsLength - 8); + fread(&entitlementsData[0], entitlementsLength - 8, 1, machoFile); + break; + } + } + } + + break; + } + + offset += cmd.cmdsize; + } + } + } + + fclose(machoFile); + + NSData* entitlementsNSData = nil; + + if(entitlementsData) + { + entitlementsNSData = [NSData dataWithBytes:entitlementsData length:entitlementsLength]; + free(entitlementsData); + } + + if(entitlementsNSData) + { + NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:entitlementsNSData options:NSPropertyListImmutable format:nil error:nil]; + NSLog(@"%@ dumped entitlements %@", binaryPath, plist); + return plist; + } + else + { + NSLog(@"Failed to dump entitlements of %@... This is bad", binaryPath); + } - NSLog(@"- dump error output start -"); - - printMultilineNSString(errorOutput); - - NSLog(@"- dump error output end -"); - - NSLog(@"- dumped entitlements output start -"); - - printMultilineNSString(output); - - NSLog(@"- dumped entitlements output end -"); - - return output; -} - -NSDictionary* dumpEntitlementsDict(NSString* binaryPath) -{ - NSString* entitlementsString = dumpEntitlements(binaryPath); - NSData* plistData = [entitlementsString dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSPropertyListFormat format; - NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:&format error:&error]; - return plist; + return nil; } BOOL signApp(NSString* appPath, NSError** error) @@ -227,8 +345,8 @@ BOOL signApp(NSString* appPath, NSError** error) NSString* errorOutput; int ldidRet; - NSString* entitlements = dumpEntitlements(executablePath); - if(entitlements.length == 0) + NSDictionary* entitlements = dumpEntitlements(executablePath); + if(!entitlements) { NSLog(@"app main binary has no entitlements, signing app with fallback entitlements..."); // app has no entitlements, sign with fallback entitlements diff --git a/Helper/uicache.m b/Helper/uicache.m index 7199274..1a2f8d9 100644 --- a/Helper/uicache.m +++ b/Helper/uicache.m @@ -6,7 +6,7 @@ // uicache on steroids -extern NSDictionary* dumpEntitlementsDict(NSString* binaryPath); +extern NSDictionary* dumpEntitlements(NSString* binaryPath); NSDictionary* constructGroupsContainersForEntitlements(NSDictionary* entitlements, BOOL systemGroups) { @@ -118,7 +118,7 @@ void registerPath(char* cPath, int unregister) // Add entitlements NSString* appExecutablePath = [path stringByAppendingPathComponent:appInfoPlist[@"CFBundleExecutable"]]; - NSDictionary* entitlements = dumpEntitlementsDict(appExecutablePath); + NSDictionary* entitlements = dumpEntitlements(appExecutablePath); if(entitlements) { dictToRegister[@"Entitlements"] = entitlements; @@ -185,7 +185,7 @@ void registerPath(char* cPath, int unregister) // Add entitlements NSString* pluginExecutablePath = [pluginPath stringByAppendingPathComponent:pluginInfoPlist[@"CFBundleExecutable"]]; - NSDictionary* pluginEntitlements = dumpEntitlementsDict(pluginExecutablePath); + NSDictionary* pluginEntitlements = dumpEntitlements(pluginExecutablePath); if(pluginEntitlements) { pluginDict[@"Entitlements"] = pluginEntitlements; diff --git a/Installer/TrollInstaller/TrollInstaller/ViewController.m b/Installer/TrollInstaller/TrollInstaller/ViewController.m index c656e6f..9a9303d 100644 --- a/Installer/TrollInstaller/TrollInstaller/ViewController.m +++ b/Installer/TrollInstaller/TrollInstaller/ViewController.m @@ -239,6 +239,8 @@ int writeRemountPrivatePreboot(void) [self updateStatus:@"Done!"]; + NSLog(@"%@", helperOutput); + // Print installed message if(ret == 0) { diff --git a/PersistenceHelper/TSPHRootViewController.m b/PersistenceHelper/TSPHRootViewController.m index bbe5ed5..e08a4e9 100644 --- a/PersistenceHelper/TSPHRootViewController.m +++ b/PersistenceHelper/TSPHRootViewController.m @@ -76,6 +76,19 @@ [refreshAppRegistrationsSpecifier setProperty:@YES forKey:@"enabled"]; refreshAppRegistrationsSpecifier.buttonAction = @selector(refreshAppRegistrations); [_specifiers addObject:refreshAppRegistrationsSpecifier]; + + PSSpecifier* uninstallTrollStoreSpecifier = [PSSpecifier preferenceSpecifierNamed:@"Uninstall TrollStore" + target:self + set:nil + get:nil + detail:nil + cell:PSButtonCell + edit:nil]; + uninstallTrollStoreSpecifier.identifier = @"uninstallTrollStore"; + [uninstallTrollStoreSpecifier setProperty:@YES forKey:@"enabled"]; + [uninstallTrollStoreSpecifier setProperty:NSClassFromString(@"PSDeleteButtonCell") forKey:@"cellClass"]; + uninstallTrollStoreSpecifier.buttonAction = @selector(uninstallTrollStorePressed); + [_specifiers addObject:uninstallTrollStoreSpecifier]; } else { @@ -201,6 +214,22 @@ [downloadTask resume]; } +- (void)uninstallTrollStorePressed +{ + UIAlertController* uninstallWarningAlert = [UIAlertController alertControllerWithTitle:@"Warning" message:@"About to uninstall TrollStore and all of the apps installed by it. Continue?" preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; + [uninstallWarningAlert addAction:cancelAction]; + + UIAlertAction* continueAction = [UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) + { + spawnRoot(helperPath(), @[@"uninstall-trollstore"]); + exit(0); + }]; + [uninstallWarningAlert addAction:continueAction]; + + [self presentViewController:uninstallWarningAlert animated:YES completion:nil]; +} - (void)uninstallPersistenceHelperPressed { diff --git a/_compile/build_full.sh b/_compile/build_full.sh index 2da9433..67ac02d 100755 --- a/_compile/build_full.sh +++ b/_compile/build_full.sh @@ -47,3 +47,19 @@ cd out COPYFILE_DISABLE=1 tar -czvf TrollStore.tar ./TrollStore.app rm -rf ./TrollStore.app cd - + +# Step five: compile installer +xcodebuild -project ../Installer/TrollInstaller/TrollInstaller.xcodeproj -scheme TrollInstaller -destination generic/platform=iOS -archivePath ./out/Installer.xcarchive archive + +if [[ -f "./out/Installer.xcarchive/Products/Applications/TrollInstaller.app/embedded.mobileprovision" ]]; then + rm ./out/Installer.xcarchive/Products/Applications/TrollInstaller.app/embedded.mobileprovision +fi + +ldid -s ./out/Installer.xcarchive/Products/Applications/TrollInstaller.app +mkdir ./out/Payload +mv ./out/Installer.xcarchive/Products/Applications/TrollInstaller.app ./out/Payload/TrollInstaller.app +cd out +zip -vr TrollInstaller.ipa Payload +cd - +rm -rf ./out/Payload +rm -rf ./out/Installer.xcarchive \ No newline at end of file From 57d18dde7266c9a8d945cc213a70ffd84a7b1079 Mon Sep 17 00:00:00 2001 From: opa334 Date: Sun, 4 Sep 2022 00:53:15 +0200 Subject: [PATCH 6/8] 1.0.3 --- Helper/control | 2 +- PersistenceHelper/Resources/Info.plist | 2 +- PersistenceHelper/control | 2 +- Store/Resources/Info.plist | 2 +- Store/control | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Helper/control b/Helper/control index 1c45e31..d89e573 100644 --- a/Helper/control +++ b/Helper/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstoreroothelper Name: trollstoreroothelper -Version: 1.0.2 +Version: 1.0.3 Architecture: iphoneos-arm Description: An awesome tool of some sort!! Maintainer: opa334 diff --git a/PersistenceHelper/Resources/Info.plist b/PersistenceHelper/Resources/Info.plist index ca48dbd..597db31 100644 --- a/PersistenceHelper/Resources/Info.plist +++ b/PersistenceHelper/Resources/Info.plist @@ -52,7 +52,7 @@ iPhoneOS CFBundleVersion - 1.0.2 + 1.0.3 LSRequiresIPhoneOS UIDeviceFamily diff --git a/PersistenceHelper/control b/PersistenceHelper/control index e805279..7315cc5 100644 --- a/PersistenceHelper/control +++ b/PersistenceHelper/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstorehelper Name: TrollStore Helper -Version: 1.0.2 +Version: 1.0.3 Architecture: iphoneos-arm Description: Helper app to install and manage TrollStore! Maintainer: opa334 diff --git a/Store/Resources/Info.plist b/Store/Resources/Info.plist index 639658b..a399920 100644 --- a/Store/Resources/Info.plist +++ b/Store/Resources/Info.plist @@ -50,7 +50,7 @@ iPhoneOS CFBundleVersion - 1.0.2 + 1.0.3 LSRequiresIPhoneOS UIDeviceFamily diff --git a/Store/control b/Store/control index b69c4ba..68b7e5c 100644 --- a/Store/control +++ b/Store/control @@ -1,6 +1,6 @@ Package: com.opa334.trollstore Name: TrollStore -Version: 1.0.2 +Version: 1.0.3 Architecture: iphoneos-arm Description: An awesome application! Maintainer: opa334 From 3fbed055927e766c05da236b2368ef050d9f94cc Mon Sep 17 00:00:00 2001 From: opa334 Date: Sun, 4 Sep 2022 01:00:43 +0200 Subject: [PATCH 7/8] small silent fix for TrollHelper --- PersistenceHelper/TSPHRootViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PersistenceHelper/TSPHRootViewController.m b/PersistenceHelper/TSPHRootViewController.m index e08a4e9..4fcbfb7 100644 --- a/PersistenceHelper/TSPHRootViewController.m +++ b/PersistenceHelper/TSPHRootViewController.m @@ -224,7 +224,7 @@ UIAlertAction* continueAction = [UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleDestructive handler:^(UIAlertAction* action) { spawnRoot(helperPath(), @[@"uninstall-trollstore"]); - exit(0); + [self reloadSpecifiers]; }]; [uninstallWarningAlert addAction:continueAction]; From b85398e4fa582516ce77aa7309fd23d5cf95ea65 Mon Sep 17 00:00:00 2001 From: opa334 Date: Sun, 4 Sep 2022 01:32:06 +0200 Subject: [PATCH 8/8] Sort iPads --- Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c b/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c index 1dc0c93..99b328f 100644 --- a/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c +++ b/Installer/TrollInstaller/TrollInstaller/exploit/IOGPU.c @@ -63,9 +63,9 @@ int IOGPU_get_command_queue_extra_refills_needed(void) || strstr(u.machine, "iPhone12,") || strstr(u.machine, "iPhone13,") || strstr(u.machine, "iPhone14,") - || strstr(u.machine, "iPad13,") || strstr(u.machine, "iPad7,") || strstr(u.machine, "iPad12,") + || strstr(u.machine, "iPad13,") ) { return 1;