From 4592e76513ffdf1749d6c92baa56db1c53f0d9b5 Mon Sep 17 00:00:00 2001 From: Sam Bingner Date: Fri, 17 May 2019 20:14:52 -1000 Subject: Update uicache --- uicache.mm | 454 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- uicache.xml | 11 +- 2 files changed, 440 insertions(+), 25 deletions(-) diff --git a/uicache.mm b/uicache.mm index 54317a1..cae99d2 100644 --- a/uicache.mm +++ b/uicache.mm @@ -54,13 +54,16 @@ /* }}} */ #import - -#include -#include -#include -#include - -#include +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #include "csstore.hpp" @@ -97,6 +100,12 @@ @end +@interface LSApplicationProxy : NSObject +- (NSString*) applicationIdentifier; +- (NSURL*) resourcesDirectoryURL; +- (NSDate*) registeredDate; +@end + @interface LSApplicationWorkspace : NSObject + (id) defaultWorkspace; - (BOOL) registerApplication:(id)application; @@ -105,16 +114,188 @@ - (BOOL) registerApplicationDictionary:(id)application; - (BOOL) installApplication:(id)application withOptions:(id)options; - (BOOL) _LSPrivateRebuildApplicationDatabasesForSystemApps:(BOOL)system internal:(BOOL)internal user:(BOOL)user; +- (NSArray*) allApplications; +@end + +@interface MCMAppDataContainer ++(id)containerWithIdentifier:(NSString*)identifier createIfNecessary:(bool)create existed:(bool*)existed error:(NSError*)error; +-(NSURL*)url; +@end + +@interface FBSSystemService ++(id)sharedService; +-(void)sendActions:(NSSet*)actions withResult:(id)result; @end -int main(int argc, const char *argv[]) { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +typedef enum { + None = 0, + RestartRenderServer = (1 << 0), // also relaunch backboardd + SnapshotTransition = (1 << 1), + FadeToBlackTransition = (1 << 2), +} SBSRelaunchActionStyle; + +@interface SBSRelaunchAction ++(id)actionWithReason:(id)reason options:(int64_t)options targetURL:(NSURL*)url; +@end - Class $LSApplicationWorkspace(objc_getClass("LSApplicationWorkspace")); - LSApplicationWorkspace *workspace($LSApplicationWorkspace == nil ? nil : [$LSApplicationWorkspace defaultWorkspace]); +static int verbose=0; +static int standard_uicache(void); +static Class $MCMPluginKitPluginDataContainer; +static Class $MCMAppDataContainer; +static Class $LSApplicationWorkspace; +LSApplicationWorkspace *workspace=nil; + +NSString *getAppPath(NSString *path) +{ + path = [path stringByResolvingSymlinksInPath]; + if (![path hasPrefix:@"/Applications/"]) { + fprintf(stderr, "Error: Path must be within /Applications/\n"); + return nil; + } + return [NSString pathWithComponents:[[path pathComponents] subarrayWithRange:NSMakeRange(0, 3)]]; +} + +bool appIsRegistered(NSString *path) +{ + @autoreleasepool { + path = getAppPath(path); + if (!path) return false; + for (LSApplicationProxy *app in [workspace allApplications]) { + if ([path isEqualToString:[[app resourcesDirectoryURL] path]]) return true; + } + return false; + } +} + +bool unregisterPath(NSString *path) +{ + @autoreleasepool { + if (verbose) fprintf(stderr, "Unregistering %s\n", path.lastPathComponent.UTF8String); + path = getAppPath(path); + if (!path) return false; + if (appIsRegistered(path) && ![workspace unregisterApplication:[NSURL fileURLWithPath:path]]) { + fprintf(stderr, "Error: unregisterApplication failed for %s\n", path.lastPathComponent.UTF8String); + return false; + } + } + return true; +} + +// Credit to coolstar for finding how to do this and not sharing with the community thereby forcing me to figure out how to do the same thing. +bool registerPath(NSString *path) +{ + if (!path) { + if (verbose) fprintf(stderr, "registerPath called with no path\n"); + return false; + } + NSString *realPath = getAppPath(path); + if (!realPath) { + if (verbose) fprintf(stderr, "unable to determine path for %s\n", path.UTF8String); + return false; + } + NSDictionary *infoDictionary = [NSDictionary dictionaryWithContentsOfFile: + [realPath stringByAppendingPathComponent:@"Info.plist"]]; + NSString *bundleID = [infoDictionary objectForKey:@"CFBundleIdentifier"]; + + if (bundleID) { + NSFileManager *fm = [NSFileManager defaultManager]; + + if ([infoDictionary objectForKey:@"CFBundleExecutable"]) { + NSString *executable = [realPath stringByAppendingPathComponent:[infoDictionary objectForKey:@"CFBundleExecutable"]]; + if (![fm fileExistsAtPath:executable]) { + fprintf(stderr, "Error: CFBundleExecutable defined but missing for %s - this is a fatal error. Aborting.\n", realPath.lastPathComponent.UTF8String); + return false; + } + } + + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"System", @"ApplicationType", + @YES, @"BundleNameIsLocalized", + bundleID, @"CFBundleIdentifier", + @NO, @"CompatibilityState", + @NO, @"IsDeletable", + realPath, @"Path", + [NSMutableDictionary dictionary], @"_LSBundlePlugins", + nil]; + + id appContainer = [$MCMAppDataContainer containerWithIdentifier:bundleID + createIfNecessary:YES existed:NULL error:nil]; + + NSString *appContainerPath = [[appContainer url] path]; + if (appContainerPath) { + dict[@"Container"] = appContainerPath; + } + NSString *pluginsPath = [realPath stringByAppendingPathComponent:@"PlugIns"]; + for (NSString *plugin in [fm contentsOfDirectoryAtPath:pluginsPath error:nil]) { + NSString *pluginPath = [pluginsPath stringByAppendingPathComponent:plugin]; + NSString *pluginInfoPlistPath = [pluginPath stringByAppendingPathComponent:@"Info.plist"]; + NSString *pluginBundleIdentifier = [[NSDictionary dictionaryWithContentsOfFile:pluginInfoPlistPath] objectForKey:@"CFBundleIdentifier"]; + if (pluginBundleIdentifier) { + id pluginContainer = [$MCMPluginKitPluginDataContainer containerWithIdentifier:pluginBundleIdentifier + createIfNecessary:YES existed:NULL error:nil]; + NSURL *pluginContainerURL = [pluginContainer url]; + NSString *pluginContainerPath = [pluginContainerURL path]; + dict[@"_LSBundlePlugins"][pluginBundleIdentifier] = @{ + @"ApplicationType": @"PluginKitPlugin", + @"BundleNameIsLocalized": @YES, + @"CFBundleIdentifier": pluginBundleIdentifier, + @"CompatibilityState": @NO, + @"Container": pluginContainerPath, + @"Path": pluginPath, + @"PluginOwnerBundleID": bundleID + }; + } + } + if (![[$LSApplicationWorkspace defaultWorkspace] registerApplicationDictionary:dict]) { + fprintf(stderr, "Error: registerApplicationDictionary failed for %s\n", path.lastPathComponent.UTF8String); + return false; + } + } else { + return unregisterPath(realPath); + } + return true; +} + +void usage(void) +{ + fprintf(stderr, "Usage: %s [-hrv] [[-p | -u] /Applications/App.app]]\n", getprogname()); + exit(EXIT_FAILURE); +} + +pid_t launch_get_job_pid(const char * job) +{ + launch_data_t resp; + launch_data_t msg; + + msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + if (msg == NULL) { + return -1; + } + + launch_data_dict_insert(msg, launch_data_new_string(job), LAUNCH_KEY_GETJOB); + + resp = launch_msg(msg); + launch_data_free(msg); + + if (resp == NULL) { + return -1; + } + + if (launch_data_get_type(resp) != LAUNCH_DATA_DICTIONARY) return -1; + + launch_data_t pid_data = launch_data_dict_lookup(resp, "PID"); + if (launch_data_get_type(pid_data) != LAUNCH_DATA_INTEGER) return -1; + + pid_t pid = (pid_t)launch_data_get_integer(pid_data); + launch_data_free(resp); + return pid; +} - if (kCFCoreFoundationVersionNumber > 1000) // this API is on iOS 7 but invaliding the icon cache is harder there - if ([workspace respondsToSelector:@selector(_LSPrivateRebuildApplicationDatabasesForSystemApps:internal:user:)]) { +int standard_uicache(void) +{ + @autoreleasepool { + if (kCFCoreFoundationVersionNumber > 1000 && // this API is on iOS 7 but invaliding the icon cache is harder there + [workspace respondsToSelector:@selector(_LSPrivateRebuildApplicationDatabasesForSystemApps:internal:user:)]) { if (![workspace _LSPrivateRebuildApplicationDatabasesForSystemApps:YES internal:YES user:NO]) fprintf(stderr, "failed to rebuild application databases"); return 0; @@ -242,12 +423,247 @@ int main(int argc, const char *argv[]) { system("killall -SIGCONT SpringBoard"); } - if (respring) - system("launchctl stop com.apple.SpringBoard"); - else - notify_post("com.apple.mobile.application_installed"); - - [pool release]; + notify_post("com.apple.mobile.application_installed"); return 0; + } +} + +pid_t pidOfCydia(void) { + launch_data_t request = launch_data_new_string(LAUNCH_KEY_GETJOBS); + launch_data_t response = launch_msg(request); + launch_data_free(request); + __block pid_t pid=-1; + + if (response == NULL || launch_data_get_type(response) != LAUNCH_DATA_DICTIONARY) return -1; + + xpc_dictionary_apply((xpc_object_t)response, ^bool(const char *key, xpc_object_t value) { + if (xpc_get_type(value) == XPC_TYPE_DICTIONARY) { + const char *program = xpc_dictionary_get_string(value, "Program"); + if (program && strcmp(program, "/Applications/Cydia.app/Cydia") == 0) { + pid = (pid_t)xpc_dictionary_get_int64(value, "PID"); + if (verbose) fprintf(stderr, "Found Cydia running with PID: %d\n", pid); + return false; + } + } + return true; + }); + + if (pid>0) { + return pid; + } + return -1; +} + +int optimized_uicache(void) { + __block int rv=0; + NSMutableDictionary *registered = [NSMutableDictionary new]; + static void (^readHandler)(NSFileHandle*) = ^(NSFileHandle *fh) { + NSData *output = [fh readDataToEndOfFile]; + if (output.length==0) return; + const char *found_path = (const char *)[output bytes]; + NSArray *found = [@(found_path) pathComponents]; + if (found.count >= 3) { + NSString *appPath = [@"/Applications" stringByAppendingPathComponent:found[2]]; + @synchronized (registered) { + if (registered[appPath]) return; + if (verbose) fprintf(stderr, "Updating %s\n", appPath.lastPathComponent.UTF8String); + registered[appPath] = @YES; + } + pid_t cydia_pid; + if ([found[2] isEqualToString:@"Cydia.app"] && + (cydia_pid = pidOfCydia()) > 0) { + // We are in cydia and trying to refresh it - this will kill it. Let's schedule it for later. + if (verbose) fprintf(stderr, "Waiting to refresh Cydia...\n"); + pid_t pid = fork(); + if (pid == 0) { + setpgrp(); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + fclose(stdin); + freopen("/dev/null", "a", stderr); + freopen("/dev/null", "a", stdout); + pid = fork(); + if (pid == 0) { + while (kill(cydia_pid, 0)==0) { + sleep(1); + } + const char *uicache = (*_NSGetArgv())[0]; + execl(uicache, uicache, "-vvvvvvv", NULL); + fprintf(stderr, "Unable to exec\n"); + fflush(stderr); + exit(-1); + } + exit(0); + } else if (pid > 0) { + int stat; + waitpid(pid, &stat, 0); + return; + } else { + fprintf(stderr, "Unable to fork\n"); + } + } + if (!registerPath(appPath)) rv++; + } + }; + NSFileManager *fm = [NSFileManager defaultManager]; + NSMutableDictionary *apps = [NSMutableDictionary new]; + NSMutableArray *cleanup = [NSMutableArray new]; + NSMutableArray *finds = [NSMutableArray new]; + if (verbose>1) fprintf(stderr, "Enumerating apps\n"); + for (LSApplicationProxy *app in [workspace allApplications]) { + NSString *path = [[app resourcesDirectoryURL] path]; + if (![path hasPrefix:@"/Applications/"]) continue; + if (verbose>1) fprintf(stderr, "Checking %s\n", path.lastPathComponent.UTF8String); + + NSDate *lastRegistered = [app registeredDate]; + + if ([fm fileExistsAtPath:path]) { + // Check for updated components + NSTask *find = [NSTask new]; + [find setLaunchPath:@"/usr/bin/find"]; + [find setStandardOutput:[NSPipe pipe]]; + NSString *stampPath = [NSString stringWithFormat:@"/var/tmp/uicache.stamp.%@", app.applicationIdentifier]; + [fm createFileAtPath:stampPath contents:nil attributes:@{NSFileModificationDate: lastRegistered}]; + [cleanup addObject:stampPath]; + [find setArguments:@[ path, @"-newer", stampPath, @"-print0", @"-quit"]]; + [finds addObject:find]; + [find launch]; + apps[path.lastPathComponent] = app; + } else { + if (verbose) fprintf(stderr, "De-registering removed app: %s\n", path.lastPathComponent.UTF8String); + @synchronized (registered) { + if (registered[path]) continue; + registered[path] = @YES; + } + if (!unregisterPath(path)) rv++; + } + } + + for (NSString* existing in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/Applications" error:nil]) { + NSString *path = [@"/Applications" stringByAppendingPathComponent:existing]; + if (apps[existing] || registered[path] || ![existing hasSuffix:@".app"]) continue; + if (verbose) fprintf(stderr, "Registering new app: %s\n", existing.UTF8String); + @synchronized (registered) { + registered[path] = @YES; + } + if (!registerPath(path)) rv++; + } + for (NSTask *find in finds) { + if (verbose>2) fprintf(stderr, "waiting for find %s\n", [find.arguments componentsJoinedByString:@" "].UTF8String); + readHandler([find.standardOutput fileHandleForReading]); + [find waitUntilExit]; + } + for (NSString *path in cleanup) { + [fm removeItemAtPath:path error:nil]; + } + return rv; +} + +int main(int argc, const char *argv[]) +{ + if (getuid() == 0) { + // Be mobile + if (setuid(501)) { + fprintf(stderr, "Error: unable to become mobile"); + return -1; + } + } + dlopen("/System/Library/PrivateFrameworks/MobileContainerManager.framework/MobileContainerManager", RTLD_LAZY); + dlopen("/System/Library/PrivateFrameworks/FrontBoardServices.framework/FrontBoardServices", RTLD_LAZY); + dlopen("/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices", RTLD_LAZY); + Class $SBSRelaunchAction = objc_getClass("SBSRelaunchAction"); + Class $FBSSystemService = objc_getClass("FBSSystemService"); + $MCMPluginKitPluginDataContainer = objc_getClass("MCMPluginKitPluginDataContainer"); + $MCMAppDataContainer = objc_getClass("MCMAppDataContainer"); + $LSApplicationWorkspace = objc_getClass("LSApplicationWorkspace"); + workspace = [$LSApplicationWorkspace defaultWorkspace]; + + static int rv=0; + @autoreleasepool { + bool respring=false, do_all=false; + void (*jb_oneshot_entitle_now)(pid_t a, uint64_t b); + void *libjb = dlopen("/usr/lib/libjailbreak.dylib", RTLD_LAZY); + + if (libjb) { + dlerror(); + jb_oneshot_entitle_now = (void (*)(pid_t, uint64_t))dlsym(libjb, "jb_oneshot_entitle_now"); + if (!dlerror()) jb_oneshot_entitle_now(getpid(), 2); + } + + NSMutableDictionary *paths = [NSMutableDictionary new]; + NSMutableDictionary *unregister_paths = [NSMutableDictionary new]; + static struct option long_options[] = + { + {"all", no_argument, 0, 'a'}, + {"help", no_argument, 0, 'h'}, + {"path", required_argument, 0, 'p'}, + {"unregister", required_argument, 0, 'u'}, + {"respring", no_argument, 0, 'r'}, + {"verbose", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + int option_index = 0; + char ch; + bool have_path = false; + while ((ch = getopt_long(argc, (char *const *)argv, "ap:ru:vh?", long_options, &option_index)) != -1) { + switch (ch) + { + case 'a': + do_all = true; + break; + case 'h': + usage(); + break; + case 'p': + paths[@(optarg)] = @YES; + have_path = true; + break; + case 'r': + respring = true; + break; + case 'u': + unregister_paths[@(optarg)] = @YES; + have_path = true; + break; + case 'v': + verbose++; + break; + default: + break; + } + } + if (do_all || !$MCMPluginKitPluginDataContainer || !$MCMAppDataContainer || !$LSApplicationWorkspace) { + rv = standard_uicache(); + } else if (have_path) { + for (NSString *path in [paths allKeys]) { + if (verbose) fprintf(stderr, "Refreshing %s\n", path.UTF8String); + if (!registerPath(path)) rv++; + } + for (NSString *path in [unregister_paths allKeys]) { + if (!unregisterPath(path)) rv++; + } + } else { + rv += optimized_uicache(); + } + if ( respring ) + { + pid_t sb_pid = launch_get_job_pid("com.apple.SpringBoard"); + if ($SBSRelaunchAction && $FBSSystemService) { + id action = [$SBSRelaunchAction actionWithReason:@"respring" options:RestartRenderServer targetURL:nil]; + id sharedService = [$FBSSystemService sharedService]; + [sharedService sendActions:[NSSet setWithObject:action] withResult:nil]; + for (int i=0; i<100; i++) { + if (kill(sb_pid, 0)) { + break; + } + usleep(1000); + } + } else { + system("launchctl stop com.apple.SpringBoard"); + system("launchctl stop com.apple.backboardd"); + } + } + } // @autoreleasepool + return rv; } diff --git a/uicache.xml b/uicache.xml index 898d3be..3dffa85 100644 --- a/uicache.xml +++ b/uicache.xml @@ -4,23 +4,22 @@ com.apple.private.mobileinstall.allowedSPI InstallForLaunchServices + UninstallForLaunchServices - com.apple.lsapplicationworkspace.rebuildappdatabases - com.apple.private.MobileContainerManager.allowed - com.apple.private.kernel.override-cpumon - com.apple.vpn.installer_events - + com.apple.frontboard.launchapplications + + com.apple.frontboard.shutdown + platform-application - com.apple.private.skip-library-validation -- cgit v1.2.3