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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%0A8oCCj5N*VN1)$f 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 E0M32XdjJ3c 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;