Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
168a89cb8a |
No files matched your search
@@ -407,6 +407,8 @@ static id hook_unZipFilePassword(id self, SEL _cmd, id zipPath, id toPath, id cu
|
|||||||
@interface LSApplicationWorkspace : NSObject
|
@interface LSApplicationWorkspace : NSObject
|
||||||
+ (id)defaultWorkspace;
|
+ (id)defaultWorkspace;
|
||||||
- (NSArray *)allApplications;
|
- (NSArray *)allApplications;
|
||||||
|
- (NSArray *)allInstalledApplications;
|
||||||
|
- (void)enumerateApplicationsOfType:(NSUInteger)type block:(void (^)(id proxy))block;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// --- Helper: find app bundle path from bundleId ---
|
// --- Helper: find app bundle path from bundleId ---
|
||||||
@@ -490,6 +492,114 @@ static NSString *findIconPath(NSString *bundlePath, NSDictionary *infoPlist) {
|
|||||||
return bestIcon;
|
return bestIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LaunchServices changed several proxy getters on iOS 26. Derive the bundle
|
||||||
|
// identifier from every available source instead of dropping the proxy.
|
||||||
|
static NSString *WQProxyString(id proxy, NSString *selectorName)
|
||||||
|
{
|
||||||
|
SEL selector = NSSelectorFromString(selectorName);
|
||||||
|
if (!proxy || ![proxy respondsToSelector:selector]) return nil;
|
||||||
|
id value = ((id(*)(id, SEL))objc_msgSend)(proxy, selector);
|
||||||
|
return [value isKindOfClass:NSString.class] ? value : nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSString *WQBundleIdentifierFromProxy(id proxy)
|
||||||
|
{
|
||||||
|
if (!proxy) return nil;
|
||||||
|
NSArray<NSString *> *selectors = @[
|
||||||
|
@"applicationIdentifier", @"bundleIdentifier", @"bundleId",
|
||||||
|
@"applicationID",
|
||||||
|
];
|
||||||
|
for (NSString *name in selectors) {
|
||||||
|
NSString *value = WQProxyString(proxy, name);
|
||||||
|
if (value.length) return value;
|
||||||
|
}
|
||||||
|
NSArray<NSString *> *keys = @[
|
||||||
|
@"bundleIdentifier", @"applicationIdentifier",
|
||||||
|
@"_bundleIdentifier", @"_applicationIdentifier",
|
||||||
|
];
|
||||||
|
for (NSString *key in keys) {
|
||||||
|
@try {
|
||||||
|
id value = [proxy valueForKey:key];
|
||||||
|
if ([value isKindOfClass:NSString.class] && [value length])
|
||||||
|
return value;
|
||||||
|
} @catch (__unused NSException *exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SEL urlSelector = NSSelectorFromString(@"bundleURL");
|
||||||
|
if ([proxy respondsToSelector:urlSelector]) {
|
||||||
|
id url = ((id(*)(id, SEL))objc_msgSend)(proxy, urlSelector);
|
||||||
|
if ([url isKindOfClass:NSURL.class]) {
|
||||||
|
NSString *plistPath = [[(NSURL *)url path]
|
||||||
|
stringByAppendingPathComponent:@"Info.plist"];
|
||||||
|
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:
|
||||||
|
plistPath];
|
||||||
|
NSString *identifier = info[@"CFBundleIdentifier"];
|
||||||
|
if ([identifier isKindOfClass:NSString.class] && identifier.length)
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSArray *WQLSApplicationSources(id workspace, NSArray *original)
|
||||||
|
{
|
||||||
|
static __thread BOOL collecting;
|
||||||
|
if (collecting) return original ?: @[];
|
||||||
|
collecting = YES;
|
||||||
|
|
||||||
|
NSMutableArray *result = [NSMutableArray array];
|
||||||
|
NSMutableSet<NSValue *> *seen = [NSMutableSet set];
|
||||||
|
void (^append)(id) = ^(id proxy) {
|
||||||
|
if (!proxy) return;
|
||||||
|
NSValue *value = [NSValue valueWithNonretainedObject:proxy];
|
||||||
|
if ([seen containsObject:value]) return;
|
||||||
|
[seen addObject:value];
|
||||||
|
[result addObject:proxy];
|
||||||
|
};
|
||||||
|
for (id proxy in original ?: @[]) append(proxy);
|
||||||
|
|
||||||
|
NSArray<NSString *> *arraySelectors = @[
|
||||||
|
@"allInstalledApplications", @"unrestrictedApplications",
|
||||||
|
@"directionsApplications", @"placeholderApplications",
|
||||||
|
];
|
||||||
|
for (NSString *name in arraySelectors) {
|
||||||
|
SEL selector = NSSelectorFromString(name);
|
||||||
|
if (![workspace respondsToSelector:selector]) continue;
|
||||||
|
id value = ((id(*)(id, SEL))objc_msgSend)(workspace, selector);
|
||||||
|
if ([value isKindOfClass:NSArray.class])
|
||||||
|
for (id proxy in value) append(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray<NSString *> *typedSelectors = @[
|
||||||
|
@"applicationsOfType:", @"legacyApplicationProxiesListWithType:",
|
||||||
|
];
|
||||||
|
for (NSString *name in typedSelectors) {
|
||||||
|
SEL selector = NSSelectorFromString(name);
|
||||||
|
if (![workspace respondsToSelector:selector]) continue;
|
||||||
|
for (NSUInteger type = 0; type <= 1; type++) {
|
||||||
|
id value = ((id(*)(id, SEL, NSUInteger))objc_msgSend)(workspace,
|
||||||
|
selector, type);
|
||||||
|
if ([value isKindOfClass:NSArray.class])
|
||||||
|
for (id proxy in value) append(proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEL enumerateSelector = NSSelectorFromString(
|
||||||
|
@"enumerateApplicationsOfType:block:");
|
||||||
|
if ([workspace respondsToSelector:enumerateSelector]) {
|
||||||
|
void (^enumerateBlock)(id) = ^(id proxy) { append(proxy); };
|
||||||
|
((void(*)(id, SEL, NSUInteger, id))objc_msgSend)(workspace,
|
||||||
|
enumerateSelector, 0, enumerateBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
collecting = NO;
|
||||||
|
if (result.count != (original ?: @[]).count)
|
||||||
|
NSLog(@"[Tweak] LaunchServices sources: original=%lu merged=%lu",
|
||||||
|
(unsigned long)(original ?: @[]).count,
|
||||||
|
(unsigned long)result.count);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Hook: allApplications fallback ---
|
// --- Hook: allApplications fallback ---
|
||||||
static IMP orig_allApplications = NULL;
|
static IMP orig_allApplications = NULL;
|
||||||
static id hook_allApplications(id self, SEL _cmd) {
|
static id hook_allApplications(id self, SEL _cmd) {
|
||||||
@@ -497,15 +607,20 @@ static id hook_allApplications(id self, SEL _cmd) {
|
|||||||
? ((id(*)(id,SEL))orig_allApplications)(self, _cmd) : @[];
|
? ((id(*)(id,SEL))orig_allApplications)(self, _cmd) : @[];
|
||||||
NSMutableArray *apps = [NSMutableArray array];
|
NSMutableArray *apps = [NSMutableArray array];
|
||||||
NSMutableSet<NSString *> *known = [NSMutableSet set];
|
NSMutableSet<NSString *> *known = [NSMutableSet set];
|
||||||
for (id proxy in origResult ?: @[]) {
|
NSMutableSet<NSString *> *knownBundlePaths = [NSMutableSet set];
|
||||||
NSString *identifier = [proxy respondsToSelector:@selector(applicationIdentifier)]
|
NSArray *sources = WQLSApplicationSources(self, origResult);
|
||||||
? [proxy applicationIdentifier] : nil;
|
for (id proxy in sources) {
|
||||||
if (!identifier) identifier = [proxy respondsToSelector:@selector(bundleIdentifier)]
|
NSString *identifier = WQBundleIdentifierFromProxy(proxy);
|
||||||
? [proxy bundleIdentifier] : nil;
|
if (identifier.length) [known addObject:identifier];
|
||||||
if (identifier.length) {
|
SEL urlSelector = NSSelectorFromString(@"bundleURL");
|
||||||
[known addObject:identifier];
|
if ([proxy respondsToSelector:urlSelector]) {
|
||||||
[apps addObject:proxy];
|
id url = ((id(*)(id,SEL))objc_msgSend)(proxy, urlSelector);
|
||||||
|
if ([url isKindOfClass:NSURL.class] && [(NSURL *)url path].length)
|
||||||
|
[knownBundlePaths addObject:[(NSURL *)url path]];
|
||||||
}
|
}
|
||||||
|
// Keep proxies even when iOS 26 hides their identifier; the
|
||||||
|
// ApplicationItem hook derives it again from bundleURL/Info.plist.
|
||||||
|
[apps addObject:proxy];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apps hidden from LaunchServices enumeration are recovered from their
|
// Apps hidden from LaunchServices enumeration are recovered from their
|
||||||
@@ -528,6 +643,48 @@ static id hook_allApplications(id self, SEL _cmd) {
|
|||||||
[known addObject:bundleID];
|
[known addObject:bundleID];
|
||||||
merged++;
|
merged++;
|
||||||
}
|
}
|
||||||
|
// Some iOS 26 builds expose only bundle identifiers here.
|
||||||
|
SEL identifiersSelector = NSSelectorFromString(@"installedApplications");
|
||||||
|
if ([self respondsToSelector:identifiersSelector]) {
|
||||||
|
id identifiers = ((id(*)(id, SEL))objc_msgSend)(self,
|
||||||
|
identifiersSelector);
|
||||||
|
if ([identifiers isKindOfClass:NSArray.class]) {
|
||||||
|
for (id value in identifiers) {
|
||||||
|
if (![value isKindOfClass:NSString.class] ||
|
||||||
|
[known containsObject:value]) continue;
|
||||||
|
NSString *bundleID = value;
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
if (proxy) {
|
||||||
|
[apps addObject:proxy];
|
||||||
|
[known addObject:bundleID];
|
||||||
|
merged++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (merged > 0 || apps.count > 0) {
|
if (merged > 0 || apps.count > 0) {
|
||||||
NSLog(@"[Tweak] Apps Manager: LaunchServices=%lu worse_query merged=%lu total=%lu",
|
NSLog(@"[Tweak] Apps Manager: LaunchServices=%lu worse_query merged=%lu total=%lu",
|
||||||
(unsigned long)(origResult.count), (unsigned long)merged,
|
(unsigned long)(origResult.count), (unsigned long)merged,
|
||||||
@@ -546,6 +703,17 @@ static id hook_allApplications(id self, SEL _cmd) {
|
|||||||
NSString *bid = info[@"CFBundleIdentifier"];
|
NSString *bid = info[@"CFBundleIdentifier"];
|
||||||
if (bid) {
|
if (bid) {
|
||||||
id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier: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",
|
||||||
|
}];
|
||||||
|
}
|
||||||
if (proxy) [apps addObject:proxy];
|
if (proxy) [apps addObject:proxy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,6 +728,17 @@ static id hook_allApplications(id self, SEL _cmd) {
|
|||||||
NSString *bid = info[@"CFBundleIdentifier"];
|
NSString *bid = info[@"CFBundleIdentifier"];
|
||||||
if (bid) {
|
if (bid) {
|
||||||
id proxy = [NSClassFromString(@"LSApplicationProxy") applicationProxyForIdentifier: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",
|
||||||
|
}];
|
||||||
|
}
|
||||||
if (proxy) [apps addObject:proxy];
|
if (proxy) [apps addObject:proxy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,7 +753,13 @@ static void hook_setAppProxy(id self, SEL _cmd, id proxy) {
|
|||||||
((void(*)(id,SEL,id))orig_setAppProxy)(self, _cmd, proxy);
|
((void(*)(id,SEL,id))orig_setAppProxy)(self, _cmd, proxy);
|
||||||
|
|
||||||
NSString *bundleId = [self performSelector:NSSelectorFromString(@"bundleId")];
|
NSString *bundleId = [self performSelector:NSSelectorFromString(@"bundleId")];
|
||||||
if (!bundleId) return;
|
if (!bundleId.length) {
|
||||||
|
bundleId = WQBundleIdentifierFromProxy(proxy);
|
||||||
|
if (bundleId.length)
|
||||||
|
((void(*)(id,SEL,id))objc_msgSend)(self,
|
||||||
|
NSSelectorFromString(@"setBundleId:"), bundleId);
|
||||||
|
}
|
||||||
|
if (!bundleId.length) return;
|
||||||
|
|
||||||
NSString *bundlePath = nil;
|
NSString *bundlePath = nil;
|
||||||
NSString *currentFilePath = [self performSelector:NSSelectorFromString(@"filePath")];
|
NSString *currentFilePath = [self performSelector:NSSelectorFromString(@"filePath")];
|
||||||
@@ -675,6 +860,43 @@ static void hook_didSelectItem(id self, SEL _cmd, id browserView, id indexPath)
|
|||||||
((void(*)(id,SEL,id,id))orig_didSelectItem)(self, _cmd, browserView, indexPath);
|
((void(*)(id,SEL,id,id))orig_didSelectItem)(self, _cmd, browserView, indexPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IMP orig_appsViewDidAppear;
|
||||||
|
|
||||||
|
static void hook_appsViewDidAppear(id self, SEL _cmd, BOOL animated)
|
||||||
|
{
|
||||||
|
if (orig_appsViewDidAppear)
|
||||||
|
((void(*)(id, SEL, BOOL))orig_appsViewDidAppear)(self, _cmd, animated);
|
||||||
|
|
||||||
|
static char kReloadKey;
|
||||||
|
if (objc_getAssociatedObject(self, &kReloadKey)) return;
|
||||||
|
objc_setAssociatedObject(self, &kReloadKey, @YES,
|
||||||
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||||
|
|
||||||
|
__block NSInteger attempts = 0;
|
||||||
|
__block __weak void (^weakRetry)(void);
|
||||||
|
void (^retry)(void) = ^{
|
||||||
|
NSArray *list = nil;
|
||||||
|
SEL listSelector = NSSelectorFromString(@"fileList");
|
||||||
|
if ([self respondsToSelector:listSelector])
|
||||||
|
list = ((id(*)(id, SEL))objc_msgSend)(self, listSelector);
|
||||||
|
if ([list isKindOfClass:NSArray.class] && list.count > 0) return;
|
||||||
|
if (attempts++ >= 12) return;
|
||||||
|
SEL loadSelector = NSSelectorFromString(@"loadFilesWithSortMode:");
|
||||||
|
if ([self respondsToSelector:loadSelector])
|
||||||
|
((void(*)(id, SEL, int))objc_msgSend)(self, loadSelector, 0);
|
||||||
|
if (attempts == 1)
|
||||||
|
NSLog(@"[Tweak] Apps Manager was empty; retrying after discovery");
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),
|
||||||
|
dispatch_get_main_queue(), ^{
|
||||||
|
void (^strongRetry)(void) = weakRetry;
|
||||||
|
if (strongRetry) strongRetry();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
weakRetry = retry;
|
||||||
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC),
|
||||||
|
dispatch_get_main_queue(), retry);
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - License / Integrity Bypass
|
#pragma mark - License / Integrity Bypass
|
||||||
|
|
||||||
// Suppress "Main binary was modified" and "Not activated" alerts.
|
// Suppress "Main binary was modified" and "Not activated" alerts.
|
||||||
@@ -2091,6 +2313,11 @@ static void installHooks(void) {
|
|||||||
if (appsVC) {
|
if (appsVC) {
|
||||||
Method m = class_getInstanceMethod(appsVC, NSSelectorFromString(@"browserView:didSelectItemAtIndexPath:"));
|
Method m = class_getInstanceMethod(appsVC, NSSelectorFromString(@"browserView:didSelectItemAtIndexPath:"));
|
||||||
if (m) { orig_didSelectItem = method_getImplementation(m); method_setImplementation(m, (IMP)hook_didSelectItem); }
|
if (m) { orig_didSelectItem = method_getImplementation(m); method_setImplementation(m, (IMP)hook_didSelectItem); }
|
||||||
|
m = class_getInstanceMethod(appsVC, @selector(viewDidAppear:));
|
||||||
|
if (m) {
|
||||||
|
orig_appsViewDidAppear = method_getImplementation(m);
|
||||||
|
method_setImplementation(m, (IMP)hook_appsViewDidAppear);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"[Tweak] All hooks installed");
|
NSLog(@"[Tweak] All hooks installed");
|
||||||
|
|||||||
@@ -52,3 +52,4 @@ FOUNDATION_EXPORT void WQRefreshDiscovery(void);
|
|||||||
FOUNDATION_EXPORT NSArray<NSString *> * _Nonnull WQChildrenForRoot(NSString * _Nonnull root);
|
FOUNDATION_EXPORT NSArray<NSString *> * _Nonnull WQChildrenForRoot(NSString * _Nonnull root);
|
||||||
FOUNDATION_EXPORT NSString *_Nullable WQIdentifyContainerPath(NSString * _Nonnull path);
|
FOUNDATION_EXPORT NSString *_Nullable WQIdentifyContainerPath(NSString * _Nonnull path);
|
||||||
FOUNDATION_EXPORT NSArray<NSNumber *> * _Nonnull WQActiveGrantHandles(void);
|
FOUNDATION_EXPORT NSArray<NSNumber *> * _Nonnull WQActiveGrantHandles(void);
|
||||||
|
FOUNDATION_EXPORT NSString * _Nonnull WQFallbackDisplayName(NSString * _Nonnull bundleID);
|
||||||
+113
-3
@@ -698,7 +698,7 @@ static void WQInstallAppLinks(NSArray<NSDictionary<NSString *, NSString *> *> *a
|
|||||||
(unsigned long)uuidLinked, (unsigned long)apps.count);
|
(unsigned long)uuidLinked, (unsigned long)apps.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSString *WQFallbackDisplayName(NSString *bundleID)
|
NSString *WQFallbackDisplayName(NSString *bundleID)
|
||||||
{
|
{
|
||||||
static NSDictionary<NSString *, NSString *> *names;
|
static NSDictionary<NSString *, NSString *> *names;
|
||||||
static dispatch_once_t onceToken;
|
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)
|
static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
|
||||||
{
|
{
|
||||||
NSString *root = @"/private/var/mobile/Containers/Data/Application";
|
NSString *root = @"/private/var/mobile/Containers/Data/Application";
|
||||||
@@ -984,7 +1079,12 @@ static NSArray<NSDictionary<NSString *, NSString *> *> *WQBuildAppList(void)
|
|||||||
if (WQGrantContainer(container)) granted++;
|
if (WQGrantContainer(container)) granted++;
|
||||||
}
|
}
|
||||||
WQPostStartupProgress(0.48, @"正在从数据目录识别应用…");
|
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);
|
WQInstallAppLinks(apps);
|
||||||
NSLog(@"[WQ] app discovery containers=%lu granted=%lu apps=%lu",
|
NSLog(@"[WQ] app discovery containers=%lu granted=%lu apps=%lu",
|
||||||
(unsigned long)containers.count, (unsigned long)granted,
|
(unsigned long)containers.count, (unsigned long)granted,
|
||||||
@@ -1000,7 +1100,9 @@ NSArray<NSDictionary<NSString *, NSString *> *> *WQInstalledApplications(void)
|
|||||||
dispatch_sync(gAppsQueue, ^{
|
dispatch_sync(gAppsQueue, ^{
|
||||||
NSTimeInterval age = gAppsCacheDate
|
NSTimeInterval age = gAppsCacheDate
|
||||||
? -[gAppsCacheDate timeIntervalSinceNow] : 1e9;
|
? -[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();
|
gAppsCache = WQBuildAppList();
|
||||||
gAppsCacheDate = [NSDate date];
|
gAppsCacheDate = [NSDate date];
|
||||||
}
|
}
|
||||||
@@ -1133,6 +1235,14 @@ NSString *WQBundlePathForIdentifier(NSString *bundleID)
|
|||||||
NSDictionary *info = gBundleInfo[bundleID];
|
NSDictionary *info = gBundleInfo[bundleID];
|
||||||
if ([info[@"path"] isKindOfClass:NSString.class]) return info[@"path"];
|
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");
|
Class proxyClass = NSClassFromString(@"LSApplicationProxy");
|
||||||
id proxy = [proxyClass respondsToSelector:
|
id proxy = [proxyClass respondsToSelector:
|
||||||
NSSelectorFromString(@"applicationProxyForIdentifier:")]
|
NSSelectorFromString(@"applicationProxyForIdentifier:")]
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ def exclude_framework(name):
|
|||||||
if n in ('libgdal', 'openblas', 'libgfortran', 'libproj', 'libgeos',
|
if n in ('libgdal', 'openblas', 'libgfortran', 'libproj', 'libgeos',
|
||||||
'libgeos_c', 'libspatialindex', 'libspatialindex_c'):
|
'libgeos_c', 'libspatialindex', 'libspatialindex_c'):
|
||||||
return 'Python geo/math'
|
return 'Python geo/math'
|
||||||
# Duplicate variant frameworks (A/B/C/D/E) are runtime alternatives.
|
# Keep the A-E variants. ios_system picks a free variant for each
|
||||||
if re.search(r'(dash|perl|ssh_cmd|openrsync|xetex|luahbtex|luatex|pdftex|euptex)[A-E]$', n):
|
# concurrent script/shell/SSH session, so stripping them breaks dash
|
||||||
return 'duplicate variant'
|
# scripts and parallel sessions.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
fw_size = {}
|
fw_size = {}
|
||||||
@@ -133,6 +133,14 @@ while pending:
|
|||||||
selected.add(d)
|
selected.add(d)
|
||||||
pending.append(d)
|
pending.append(d)
|
||||||
|
|
||||||
|
# ios_system appends A-E when it starts concurrent dash scripts. These
|
||||||
|
# variants are not referenced by commandDictionary.plist, so add them
|
||||||
|
# explicitly; otherwise `dash script.sh` fails to load dashA.framework.
|
||||||
|
for variant in ('dashA', 'dashB', 'dashC', 'dashD', 'dashE'):
|
||||||
|
framework = variant + '.framework'
|
||||||
|
if framework in fw_size:
|
||||||
|
selected.add(framework)
|
||||||
|
|
||||||
print('selected frameworks:', len(selected), 'size:', round(sum(fw_size[f] for f in selected)/1e6, 1), 'MB')
|
print('selected frameworks:', len(selected), 'size:', round(sum(fw_size[f] for f in selected)/1e6, 1), 'MB')
|
||||||
|
|
||||||
# extract frameworks
|
# extract frameworks
|
||||||
|
|||||||
Reference in new issue
Block a user