App manager: database names, data sizes, faster discovery
This commit is contained in:
1 parent
168a89cb8a
commit
0e5851c521
6 files changed
+1280
-65
No files matched your search
@@ -27,6 +27,7 @@ typedef int (*WQAddChdirFn)(posix_spawn_file_actions_t *, const char *);
|
||||
#include "WQTerminal.h"
|
||||
#include "WQSSHServer.h"
|
||||
#include "WQPackages.h"
|
||||
#import "WQLSStore.h"
|
||||
|
||||
#pragma mark - Root Helper Hooks
|
||||
|
||||
@@ -600,6 +601,82 @@ static NSArray *WQLSApplicationSources(id workspace, NSArray *original)
|
||||
return result;
|
||||
}
|
||||
|
||||
// App icons: iOS's private UIKit API returns the SpringBoard icon on iOS 27.
|
||||
// iOS 26 returns a placeholder; the bundle PNG fallback is then used when the
|
||||
// bundle happens to be readable. Icons are written into the app's tmp folder
|
||||
// and exposed through ApplicationItem.iconPath.
|
||||
@interface UIImage (WQPrivateIcon)
|
||||
+ (UIImage *)_applicationIconImageForBundleIdentifier:(NSString *)bundleID
|
||||
format:(int)format
|
||||
scale:(CGFloat)scale;
|
||||
@end
|
||||
|
||||
static NSString *WQIconPathForBundleIdentifier(NSString *bundleID)
|
||||
{
|
||||
if (!bundleID.length) return nil;
|
||||
static NSMutableDictionary<NSString *, NSString *> *paths;
|
||||
static NSData *placeholder;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
paths = [NSMutableDictionary dictionary];
|
||||
if ([UIImage respondsToSelector:
|
||||
@selector(_applicationIconImageForBundleIdentifier:format:scale:)]) {
|
||||
UIImage *image = [UIImage _applicationIconImageForBundleIdentifier:
|
||||
@"com.jbjf.filza.nonexistent-icon" format:2 scale:2.0];
|
||||
if (image.CGImage) placeholder = UIImagePNGRepresentation(image);
|
||||
}
|
||||
});
|
||||
@synchronized (paths) {
|
||||
NSString *cached = paths[bundleID];
|
||||
if (cached.length) {
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:cached])
|
||||
return cached;
|
||||
[paths removeObjectForKey:bundleID];
|
||||
}
|
||||
}
|
||||
BOOL responds = [UIImage respondsToSelector:
|
||||
@selector(_applicationIconImageForBundleIdentifier:format:scale:)];
|
||||
if (getenv("WQ_APP_NAME_DEBUG")) {
|
||||
static int requestDebug;
|
||||
if (requestDebug++ < 10)
|
||||
NSLog(@"[Apps] icon request %@ responds=%d", bundleID, responds);
|
||||
}
|
||||
if (!responds) return nil;
|
||||
UIImage *image = [UIImage _applicationIconImageForBundleIdentifier:bundleID
|
||||
format:2 scale:2.0];
|
||||
if (!image.CGImage) {
|
||||
if (getenv("WQ_APP_NAME_DEBUG")) {
|
||||
static int nilDebug;
|
||||
if (nilDebug++ < 10)
|
||||
NSLog(@"[Apps] icon %@ returned no image", bundleID);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
NSData *png = UIImagePNGRepresentation(image);
|
||||
if (!png.length) return nil;
|
||||
BOOL isPlaceholder = placeholder.length && [png isEqualToData:placeholder];
|
||||
if (getenv("WQ_APP_NAME_DEBUG")) {
|
||||
static int iconDebug;
|
||||
if (iconDebug++ < 10)
|
||||
NSLog(@"[Apps] icon %@ image=%dx%d png=%lu placeholder=%d",
|
||||
bundleID, (int)image.size.width, (int)image.size.height,
|
||||
(unsigned long)png.length, isPlaceholder);
|
||||
}
|
||||
if (isPlaceholder) return nil;
|
||||
|
||||
NSString *directory = [NSTemporaryDirectory()
|
||||
stringByAppendingPathComponent:@"WQAppIcons"];
|
||||
[NSFileManager.defaultManager createDirectoryAtPath:directory
|
||||
withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
NSString *name = [bundleID stringByReplacingOccurrencesOfString:@"/"
|
||||
withString:@"_"];
|
||||
NSString *path = [directory stringByAppendingPathComponent:
|
||||
[name stringByAppendingPathExtension:@"png"]];
|
||||
if (![png writeToFile:path atomically:YES]) return nil;
|
||||
@synchronized (paths) { paths[bundleID] = path; }
|
||||
return path;
|
||||
}
|
||||
|
||||
// --- Hook: allApplications fallback ---
|
||||
static IMP orig_allApplications = NULL;
|
||||
static id hook_allApplications(id self, SEL _cmd) {
|
||||
@@ -631,13 +708,12 @@ static id hook_allApplications(id self, SEL _cmd) {
|
||||
for (NSDictionary *info in wqApps) {
|
||||
NSString *bundleID = info[@"bundleID"];
|
||||
if (!bundleID.length || [known containsObject:bundleID]) continue;
|
||||
id proxy = nil;
|
||||
if ([proxyClass respondsToSelector:
|
||||
id proxy = [[WQApplicationProxy alloc] initWithAppInfo:info];
|
||||
if (!proxy && [proxyClass respondsToSelector:
|
||||
NSSelectorFromString(@"applicationProxyForIdentifier:")]) {
|
||||
proxy = ((id(*)(id,SEL,id))objc_msgSend)(proxyClass,
|
||||
NSSelectorFromString(@"applicationProxyForIdentifier:"), bundleID);
|
||||
}
|
||||
if (!proxy) proxy = [[WQApplicationProxy alloc] initWithAppInfo:info];
|
||||
if (!proxy) continue;
|
||||
[apps addObject:proxy];
|
||||
[known addObject:bundleID];
|
||||
@@ -656,26 +732,18 @@ static id hook_allApplications(id self, SEL _cmd) {
|
||||
NSString *bundlePath = WQBundlePathForIdentifier(bundleID);
|
||||
if (bundlePath.length &&
|
||||
[knownBundlePaths containsObject:bundlePath]) continue;
|
||||
id proxy = nil;
|
||||
if ([proxyClass respondsToSelector:
|
||||
NSSelectorFromString(@"applicationProxyForIdentifier:")]) {
|
||||
proxy = ((id(*)(id,SEL,id))objc_msgSend)(proxyClass,
|
||||
NSSelectorFromString(@"applicationProxyForIdentifier:"),
|
||||
bundleID);
|
||||
}
|
||||
if (!proxy) {
|
||||
NSString *error = nil;
|
||||
NSString *dataPath = MCMFilzaDataContainerPath(bundleID,
|
||||
&error);
|
||||
NSMutableDictionary *info = [@{
|
||||
@"bundleID": bundleID,
|
||||
@"name": WQFallbackDisplayName(bundleID),
|
||||
@"version": @"1.0",
|
||||
} mutableCopy];
|
||||
if (bundlePath.length) info[@"bundlePath"] = bundlePath;
|
||||
if (dataPath.length) info[@"dataPath"] = dataPath;
|
||||
proxy = [[WQApplicationProxy alloc] initWithAppInfo:info];
|
||||
}
|
||||
NSString *error = nil;
|
||||
NSString *dataPath = MCMFilzaDataContainerPath(bundleID,
|
||||
&error);
|
||||
NSMutableDictionary *info = [@{
|
||||
@"bundleID": bundleID,
|
||||
@"name": WQLSDisplayName(bundleID) ?:
|
||||
WQFallbackDisplayName(bundleID),
|
||||
@"version": @"1.0",
|
||||
} mutableCopy];
|
||||
if (bundlePath.length) info[@"bundlePath"] = bundlePath;
|
||||
if (dataPath.length) info[@"dataPath"] = dataPath;
|
||||
id proxy = [[WQApplicationProxy alloc] initWithAppInfo:info];
|
||||
if (proxy) {
|
||||
[apps addObject:proxy];
|
||||
[known addObject:bundleID];
|
||||
@@ -702,18 +770,16 @@ static id hook_allApplications(id self, SEL _cmd) {
|
||||
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist];
|
||||
NSString *bid = info[@"CFBundleIdentifier"];
|
||||
if (bid) {
|
||||
id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier:bid];
|
||||
if (!proxy) {
|
||||
proxy = [[WQApplicationProxy alloc] initWithAppInfo:@{
|
||||
@"bundleID": bid,
|
||||
@"bundlePath": [uuidPath stringByAppendingPathComponent:item],
|
||||
@"dataPath": WQDataPathForIdentifier(bid) ?: @"",
|
||||
@"name": info[@"CFBundleDisplayName"] ?:
|
||||
info[@"CFBundleName"] ?: bid,
|
||||
@"version": info[@"CFBundleShortVersionString"] ?:
|
||||
info[@"CFBundleVersion"] ?: @"1.0",
|
||||
}];
|
||||
}
|
||||
id proxy = [[WQApplicationProxy alloc] initWithAppInfo:@{
|
||||
@"bundleID": bid,
|
||||
@"bundlePath": [uuidPath stringByAppendingPathComponent:item],
|
||||
@"dataPath": WQDataPathForIdentifier(bid) ?: @"",
|
||||
@"name": WQLSDisplayName(bid) ?:
|
||||
info[@"CFBundleDisplayName"] ?:
|
||||
info[@"CFBundleName"] ?: bid,
|
||||
@"version": info[@"CFBundleShortVersionString"] ?:
|
||||
info[@"CFBundleVersion"] ?: @"1.0",
|
||||
}];
|
||||
if (proxy) [apps addObject:proxy];
|
||||
}
|
||||
}
|
||||
@@ -727,18 +793,16 @@ static id hook_allApplications(id self, SEL _cmd) {
|
||||
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist];
|
||||
NSString *bid = info[@"CFBundleIdentifier"];
|
||||
if (bid) {
|
||||
id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier:bid];
|
||||
if (!proxy) {
|
||||
proxy = [[WQApplicationProxy alloc] initWithAppInfo:@{
|
||||
@"bundleID": bid,
|
||||
@"bundlePath": [@"/Applications" stringByAppendingPathComponent:item],
|
||||
@"dataPath": WQDataPathForIdentifier(bid) ?: @"",
|
||||
@"name": info[@"CFBundleDisplayName"] ?:
|
||||
info[@"CFBundleName"] ?: bid,
|
||||
@"version": info[@"CFBundleShortVersionString"] ?:
|
||||
info[@"CFBundleVersion"] ?: @"1.0",
|
||||
}];
|
||||
}
|
||||
id proxy = [[WQApplicationProxy alloc] initWithAppInfo:@{
|
||||
@"bundleID": bid,
|
||||
@"bundlePath": [@"/Applications" stringByAppendingPathComponent:item],
|
||||
@"dataPath": WQDataPathForIdentifier(bid) ?: @"",
|
||||
@"name": WQLSDisplayName(bid) ?:
|
||||
info[@"CFBundleDisplayName"] ?:
|
||||
info[@"CFBundleName"] ?: bid,
|
||||
@"version": info[@"CFBundleShortVersionString"] ?:
|
||||
info[@"CFBundleVersion"] ?: @"1.0",
|
||||
}];
|
||||
if (proxy) [apps addObject:proxy];
|
||||
}
|
||||
}
|
||||
@@ -781,6 +845,8 @@ static void hook_setAppProxy(id self, SEL _cmd, id proxy) {
|
||||
// bundle path.
|
||||
NSString *displayName = nil;
|
||||
if (bundlePath) {
|
||||
if (!WQGrantExtraPath(bundlePath, NO))
|
||||
WQGrantExtraPath(bundlePath.stringByDeletingLastPathComponent, NO);
|
||||
NSString *plist = [bundlePath stringByAppendingPathComponent:@"Info.plist"];
|
||||
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist];
|
||||
displayName = info[@"CFBundleDisplayName"];
|
||||
@@ -794,7 +860,15 @@ static void hook_setAppProxy(id self, SEL _cmd, id proxy) {
|
||||
|
||||
// Fix icon path
|
||||
NSString *iconPath = ((id(*)(id,SEL))objc_msgSend)(self, NSSelectorFromString(@"iconPath"));
|
||||
if (!iconPath && bundlePath) {
|
||||
if (!iconPath.length) {
|
||||
NSString *systemIcon = WQIconPathForBundleIdentifier(bundleId);
|
||||
if (systemIcon.length) {
|
||||
((void(*)(id,SEL,id))objc_msgSend)(self,
|
||||
NSSelectorFromString(@"setIconPath:"), systemIcon);
|
||||
iconPath = systemIcon;
|
||||
}
|
||||
}
|
||||
if (!iconPath.length && bundlePath) {
|
||||
NSString *plist = [bundlePath stringByAppendingPathComponent:@"Info.plist"];
|
||||
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:plist];
|
||||
NSString *found = findIconPath(bundlePath, info);
|
||||
@@ -897,6 +971,77 @@ static void hook_appsViewDidAppear(id self, SEL _cmd, BOOL animated)
|
||||
dispatch_get_main_queue(), retry);
|
||||
}
|
||||
|
||||
static IMP orig_appCalculateDiskUsage;
|
||||
static NSMutableDictionary<NSString *, NSNumber *> *gAppContainerSizes;
|
||||
static NSMutableDictionary<NSString *, NSDate *> *gAppContainerSizeDates;
|
||||
|
||||
static unsigned long long WQDirectoryAllocatedSize(NSString *path)
|
||||
{
|
||||
NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
|
||||
NSArray<NSURLResourceKey> *keys = @[
|
||||
NSURLTotalFileAllocatedSizeKey, NSURLFileSizeKey,
|
||||
];
|
||||
NSDirectoryEnumerator<NSURL *> *enumerator =
|
||||
[NSFileManager.defaultManager enumeratorAtURL:url
|
||||
includingPropertiesForKeys:keys options:0
|
||||
errorHandler:^BOOL(NSURL *url, NSError *error) { return YES; }];
|
||||
unsigned long long total = 0;
|
||||
for (NSURL *file in enumerator) {
|
||||
NSDictionary<NSURLResourceKey, id> *values =
|
||||
[file resourceValuesForKeys:keys error:nil];
|
||||
NSNumber *size = values[NSURLTotalFileAllocatedSizeKey];
|
||||
if (![size isKindOfClass:NSNumber.class])
|
||||
size = values[NSURLFileSizeKey];
|
||||
if ([size isKindOfClass:NSNumber.class])
|
||||
total += size.unsignedLongLongValue;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static unsigned long long WQAppContainerSize(NSString *path)
|
||||
{
|
||||
if (!path.length) return 0;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gAppContainerSizes = [NSMutableDictionary dictionary];
|
||||
gAppContainerSizeDates = [NSMutableDictionary dictionary];
|
||||
});
|
||||
@synchronized (gAppContainerSizes) {
|
||||
NSNumber *cached = gAppContainerSizes[path];
|
||||
NSDate *date = gAppContainerSizeDates[path];
|
||||
if (cached && date &&
|
||||
-[date timeIntervalSinceNow] < 300)
|
||||
return cached.unsignedLongLongValue;
|
||||
unsigned long long size = WQDirectoryAllocatedSize(path);
|
||||
gAppContainerSizes[path] = @(size);
|
||||
gAppContainerSizeDates[path] = [NSDate date];
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
static void hook_appCalculateDiskUsage(id self, SEL _cmd)
|
||||
{
|
||||
if (orig_appCalculateDiskUsage)
|
||||
((void(*)(id, SEL))orig_appCalculateDiskUsage)(self, _cmd);
|
||||
|
||||
NSString *container = nil;
|
||||
SEL documentSelector = NSSelectorFromString(@"documentPath");
|
||||
if ([self respondsToSelector:documentSelector])
|
||||
container = ((id(*)(id, SEL))objc_msgSend)(self, documentSelector);
|
||||
if (![container isKindOfClass:NSString.class] || !container.length) return;
|
||||
|
||||
unsigned long long size = WQAppContainerSize(container);
|
||||
static int sizeLogCount;
|
||||
if (getenv("WQ_TEST_APPS") && sizeLogCount++ < 8)
|
||||
NSLog(@"[Apps] size %@ = %llu", container.lastPathComponent, size);
|
||||
((void(*)(id, SEL, unsigned long long))objc_msgSend)(self,
|
||||
NSSelectorFromString(@"setFileSize:"), size);
|
||||
NSString *text = [NSByteCountFormatter stringFromByteCount:(long long)size
|
||||
countStyle:NSByteCountFormatterCountStyleFile];
|
||||
((void(*)(id, SEL, id))objc_msgSend)(self,
|
||||
NSSelectorFromString(@"setAFileSizeString:"), text);
|
||||
}
|
||||
|
||||
#pragma mark - License / Integrity Bypass
|
||||
|
||||
// Suppress "Main binary was modified" and "Not activated" alerts.
|
||||
@@ -2308,6 +2453,11 @@ static void installHooks(void) {
|
||||
Method m;
|
||||
m = class_getInstanceMethod(appItem, NSSelectorFromString(@"setAppProxy:"));
|
||||
if (m) { orig_setAppProxy = method_getImplementation(m); method_setImplementation(m, (IMP)hook_setAppProxy); }
|
||||
m = class_getInstanceMethod(appItem, NSSelectorFromString(@"calculateDiskUsage"));
|
||||
if (m) {
|
||||
orig_appCalculateDiskUsage = method_getImplementation(m);
|
||||
method_setImplementation(m, (IMP)hook_appCalculateDiskUsage);
|
||||
}
|
||||
}
|
||||
Class appsVC = NSClassFromString(@"TGApplicationsViewController");
|
||||
if (appsVC) {
|
||||
@@ -2496,6 +2646,24 @@ __attribute__((constructor)) void TweakInit(void) {
|
||||
installHooks();
|
||||
// Populate the MCM root before Filza restores its initial browser path.
|
||||
runMCMPath();
|
||||
if (getenv("WQ_TEST_APPS")) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(), ^{
|
||||
Class appsClass = NSClassFromString(@"TGApplicationsViewController");
|
||||
if (!appsClass) return;
|
||||
UIViewController *apps = [[appsClass alloc] init];
|
||||
UINavigationController *navigation = [[UINavigationController alloc]
|
||||
initWithRootViewController:apps];
|
||||
UIWindow *window = nil;
|
||||
for (UIWindow *candidate in UIApplication.sharedApplication.windows)
|
||||
if (candidate.isKeyWindow && !candidate.isHidden) { window = candidate; break; }
|
||||
UIViewController *root = window.rootViewController;
|
||||
while (root.presentedViewController)
|
||||
root = root.presentedViewController;
|
||||
if (root) [root presentViewController:navigation
|
||||
animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
if (getenv("WQ_TEST_SETTINGS")) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 26 * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(), ^{
|
||||
|
||||
Reference in new issue
Block a user