Fix iOS 26 app manager blank state

This commit is contained in:
admin committed 2026-11-11 15:16:54 +08:00
1 parent 53498ed08f
commit 168a89cb8a
4 files changed
+361 -15

No files matched your search

+113 -3
View File
@@ -698,7 +698,7 @@ static void WQInstallAppLinks(NSArray<NSDictionary<NSString *, NSString *> *> *a
(unsigned long)uuidLinked, (unsigned long)apps.count);
}
static NSString *WQFallbackDisplayName(NSString *bundleID)
NSString *WQFallbackDisplayName(NSString *bundleID)
{
static NSDictionary<NSString *, NSString *> *names;
static dispatch_once_t onceToken;
@@ -968,6 +968,101 @@ static void WQDiscoverPerContainerFamilies(void)
}
}
static NSDictionary<NSString *, NSDictionary *> *WQMobileInstallationApps(void)
{
static NSDictionary *apps;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
void *handle = dlopen(
"/System/Library/PrivateFrameworks/MobileInstallation.framework/MobileInstallation",
RTLD_LAZY);
if (!handle) {
NSLog(@"[WQ] MobileInstallation framework unavailable: %s", dlerror());
return;
}
NSDictionary *(*lookup)(NSDictionary *, NSError **) =
(NSDictionary *(*)(NSDictionary *, NSError **))dlsym(handle,
"MobileInstallationLookup");
if (lookup) {
NSError *error = nil;
id value = lookup(nil, &error);
if ([value isKindOfClass:NSDictionary.class]) apps = [value copy];
NSLog(@"[WQ] MobileInstallationLookup apps=%lu error=%@",
(unsigned long)apps.count,
error.localizedDescription ?: @"none");
} else {
NSLog(@"[WQ] MobileInstallationLookup symbol missing");
}
dlclose(handle);
});
return apps ?: @{};
}
static void WQAppendMobileInstallationApps(NSMutableArray *result,
NSMutableSet<NSString *> *known)
{
NSDictionary<NSString *, NSDictionary *> *apps = WQMobileInstallationApps();
for (NSString *key in apps) {
NSDictionary *entry = apps[key];
if (![entry isKindOfClass:NSDictionary.class]) continue;
NSString *bundleID = entry[@"CFBundleIdentifier"];
if (![bundleID isKindOfClass:NSString.class] || !bundleID.length)
bundleID = key;
if (!bundleID.length || [known containsObject:bundleID]) continue;
id containerValue = entry[@"Container"];
NSString *dataPath = [containerValue isKindOfClass:NSURL.class]
? [(NSURL *)containerValue path] : containerValue;
if (![dataPath isKindOfClass:NSString.class] || !dataPath.length) {
NSString *error = nil;
dataPath = MCMFilzaDataContainerPath(bundleID, &error);
}
id bundleValue = entry[@"Path"];
NSString *bundlePath = [bundleValue isKindOfClass:NSURL.class]
? [(NSURL *)bundleValue path] : bundleValue;
if (![bundlePath isKindOfClass:NSString.class] || !bundlePath.length) {
bundleValue = entry[@"BundlePath"];
bundlePath = [bundleValue isKindOfClass:NSURL.class]
? [(NSURL *)bundleValue path] : bundleValue;
}
if (![bundlePath isKindOfClass:NSString.class] || !bundlePath.length)
bundlePath = WQBundlePathForIdentifier(bundleID);
NSMutableDictionary *info = [@{
@"bundleID": bundleID,
@"name": bundleID,
@"version": @"1.0",
} mutableCopy];
if (dataPath.length) info[@"dataPath"] = dataPath;
if (bundlePath.length) {
info[@"bundlePath"] = bundlePath;
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:
[bundlePath stringByAppendingPathComponent:@"Info.plist"]];
NSString *name = plist[@"CFBundleDisplayName"];
if (!name.length) name = plist[@"CFBundleName"];
if (name.length) info[@"name"] = name;
NSString *version = plist[@"CFBundleShortVersionString"];
if (!version.length) version = plist[@"CFBundleVersion"];
if (version.length) info[@"version"] = version;
}
NSString *name = entry[@"CFBundleDisplayName"];
if (!name.length) name = entry[@"CFBundleName"];
if ([name isKindOfClass:NSString.class] && name.length)
info[@"name"] = name;
NSString *version = entry[@"CFBundleShortVersionString"];
if (!version.length) version = entry[@"CFBundleVersion"];
if ([version isKindOfClass:NSString.class] && version.length)
info[@"version"] = version;
if ([info[@"name"] isEqualToString:bundleID])
info[@"name"] = WQFallbackDisplayName(bundleID);
[result addObject:info];
[known addObject:bundleID];
}
if (apps.count)
NSLog(@"[WQ] MobileInstallation merged=%lu total=%lu",
(unsigned long)apps.count, (unsigned long)result.count);
}
static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
{
NSString *root = @"/private/var/mobile/Containers/Data/Application";
@@ -984,7 +1079,12 @@ static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
if (WQGrantContainer(container)) granted++;
}
WQPostStartupProgress(0.48, @"正在从数据目录识别应用…");
NSArray *apps = WQAppInfoFromContainers(containers);
NSArray *containerApps = WQAppInfoFromContainers(containers);
NSMutableArray *apps = [containerApps mutableCopy];
NSMutableSet<NSString *> *known = [NSMutableSet set];
for (NSDictionary *info in containerApps)
if (info[@"bundleID"]) [known addObject:info[@"bundleID"]];
WQAppendMobileInstallationApps(apps, known);
WQInstallAppLinks(apps);
NSLog(@"[WQ] app discovery containers=%lu granted=%lu apps=%lu",
(unsigned long)containers.count, (unsigned long)granted,
@@ -1000,7 +1100,9 @@ NSArray<NSDictionary<NSString *, NSString *> *> *WQInstalledApplications(void)
dispatch_sync(gAppsQueue, ^{
NSTimeInterval age = gAppsCacheDate
? -[gAppsCacheDate timeIntervalSinceNow] : 1e9;
if (!gAppsCache || age > 120) {
// Never cache an empty result: the first call can happen before the
// worse_query grants and app-container scan have finished.
if (!gAppsCache || gAppsCache.count == 0 || age > 120) {
gAppsCache = WQBuildAppList();
gAppsCacheDate = [NSDate date];
}
@@ -1133,6 +1235,14 @@ NSString *WQBundlePathForIdentifier(NSString *bundleID)
NSDictionary *info = gBundleInfo[bundleID];
if ([info[@"path"] isKindOfClass:NSString.class]) return info[@"path"];
NSDictionary *installation = WQMobileInstallationApps()[bundleID];
if ([installation isKindOfClass:NSDictionary.class]) {
NSString *path = installation[@"Path"];
if (![path isKindOfClass:NSString.class] || !path.length)
path = installation[@"BundlePath"];
if ([path isKindOfClass:NSString.class] && path.length) return path;
}
Class proxyClass = NSClassFromString(@"LSApplicationProxy");
id proxy = [proxyClass respondsToSelector:
NSSelectorFromString(@"applicationProxyForIdentifier:")]