summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Bingner <sam@bingner.com>2019-05-17 20:14:52 -1000
committerSam Bingner <sam@bingner.com>2019-05-17 20:14:52 -1000
commit4592e76513ffdf1749d6c92baa56db1c53f0d9b5 (patch)
tree5d68554773b0673160cd260895e8532581771e55
parent2d58bfc28d99cabeedfe86e8d4c2e1a5a4d175dd (diff)
Update uicache
-rw-r--r--uicache.mm454
-rw-r--r--uicache.xml11
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 <Foundation/Foundation.h>
-
-#include <notify.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <objc/runtime.h>
+#import <dlfcn.h>
+#import <notify.h>
+#import <sys/types.h>
+#import <sys/stat.h>
+#import <unistd.h>
+#import <getopt.h>
+#import <launch.h>
+#import <objc/runtime.h>
+#import <crt_externs.h>
+#import <xpc/xpc.h>
#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<LSApplicationProxy*>*) 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 @@
<key>com.apple.private.mobileinstall.allowedSPI</key>
<array>
<string>InstallForLaunchServices</string>
+ <string>UninstallForLaunchServices</string>
</array>
-
<key>com.apple.lsapplicationworkspace.rebuildappdatabases</key>
<true/>
-
<key>com.apple.private.MobileContainerManager.allowed</key>
<true/>
-
<key>com.apple.private.kernel.override-cpumon</key>
<true/>
-
<key>com.apple.vpn.installer_events</key>
<true/>
-
+ <key>com.apple.frontboard.launchapplications</key>
+ <true/>
+ <key>com.apple.frontboard.shutdown</key>
+ <true/>
<key>platform-application</key>
<true/>
-
<key>com.apple.private.skip-library-validation</key>
<true/>
</dict>