App manager: database names, data sizes, faster discovery

This commit is contained in:
admin committed 2026-11-11 15:46:27 +08:00
1 parent 168a89cb8a
commit 0e5851c521
6 files changed
+1280 -65

No files matched your search

+216 -48
View File
@@ -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(), ^{