mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 12:54:14 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			417 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			417 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXRuntimeClient.m
 | |
| //  FLEX
 | |
| //
 | |
| //  Created by Tanner on 3/22/17.
 | |
| //  Copyright © 2017 Tanner Bennett. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "FLEXRuntimeClient.h"
 | |
| #import "NSObject+FLEX_Reflection.h"
 | |
| #import "FLEXMethod.h"
 | |
| #import "NSArray+FLEX.h"
 | |
| #import "FLEXRuntimeSafety.h"
 | |
| #include <dlfcn.h>
 | |
| 
 | |
| #define Equals(a, b)    ([a compare:b options:NSCaseInsensitiveSearch] == NSOrderedSame)
 | |
| #define Contains(a, b)  ([a rangeOfString:b options:NSCaseInsensitiveSearch].location != NSNotFound)
 | |
| #define HasPrefix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == 0)
 | |
| #define HasSuffix(a, b) ([a rangeOfString:b options:NSCaseInsensitiveSearch].location == (a.length - b.length))
 | |
| 
 | |
| 
 | |
| @interface FLEXRuntimeClient () {
 | |
|     NSMutableArray<NSString *> *_imageDisplayNames;
 | |
| }
 | |
| 
 | |
| @property (nonatomic) NSMutableDictionary *bundles_pathToShort;
 | |
| @property (nonatomic) NSMutableDictionary *bundles_shortToPath;
 | |
| @property (nonatomic) NSCache *bundles_pathToClassNames;
 | |
| @property (nonatomic) NSMutableArray<NSString *> *imagePaths;
 | |
| 
 | |
| @end
 | |
| 
 | |
| /// @return success if the map passes.
 | |
| static inline NSString * TBWildcardMap_(NSString *token, NSString *candidate, NSString *success, TBWildcardOptions options) {
 | |
|     switch (options) {
 | |
|         case TBWildcardOptionsNone:
 | |
|             // Only "if equals"
 | |
|             if (Equals(candidate, token)) {
 | |
|                 return success;
 | |
|             }
 | |
|         default: {
 | |
|             // Only "if contains"
 | |
|             if (options & TBWildcardOptionsPrefix &&
 | |
|                 options & TBWildcardOptionsSuffix) {
 | |
|                 if (Contains(candidate, token)) {
 | |
|                     return success;
 | |
|                 }
 | |
|             }
 | |
|             // Only "if candidate ends with with token"
 | |
|             else if (options & TBWildcardOptionsPrefix) {
 | |
|                 if (HasSuffix(candidate, token)) {
 | |
|                     return success;
 | |
|                 }
 | |
|             }
 | |
|             // Only "if candidate starts with with token"
 | |
|             else if (options & TBWildcardOptionsSuffix) {
 | |
|                 // Case like "Bundle." where we want "" to match anything
 | |
|                 if (!token.length) {
 | |
|                     return success;
 | |
|                 }
 | |
|                 if (HasPrefix(candidate, token)) {
 | |
|                     return success;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| /// @return candidate if the map passes.
 | |
| static inline NSString * TBWildcardMap(NSString *token, NSString *candidate, TBWildcardOptions options) {
 | |
|     return TBWildcardMap_(token, candidate, candidate, options);
 | |
| }
 | |
| 
 | |
| @implementation FLEXRuntimeClient
 | |
| 
 | |
| #pragma mark - Initialization
 | |
| 
 | |
| + (instancetype)runtime {
 | |
|     static FLEXRuntimeClient *runtime;
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         runtime = [self new];
 | |
|         [runtime reloadLibrariesList];
 | |
|     });
 | |
| 
 | |
|     return runtime;
 | |
| }
 | |
| 
 | |
| - (id)init {
 | |
|     self = [super init];
 | |
|     if (self) {
 | |
|         _imagePaths = [NSMutableArray new];
 | |
|         _bundles_pathToShort = [NSMutableDictionary new];
 | |
|         _bundles_shortToPath = [NSMutableDictionary new];
 | |
|         _bundles_pathToClassNames = [NSCache new];
 | |
|     }
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| #pragma mark - Private
 | |
| 
 | |
| - (void)reloadLibrariesList {
 | |
|     unsigned int imageCount = 0;
 | |
|     const char **imageNames = objc_copyImageNames(&imageCount);
 | |
| 
 | |
|     if (imageNames) {
 | |
|         NSMutableArray *imageNameStrings = [NSMutableArray flex_forEachUpTo:imageCount map:^NSString *(NSUInteger i) {
 | |
|             return @(imageNames[i]);
 | |
|         }];
 | |
| 
 | |
|         self.imagePaths = imageNameStrings;
 | |
|         free(imageNames);
 | |
| 
 | |
|         // Sort alphabetically
 | |
|         [imageNameStrings sortUsingComparator:^NSComparisonResult(NSString *name1, NSString *name2) {
 | |
|             NSString *shortName1 = [self shortNameForImageName:name1];
 | |
|             NSString *shortName2 = [self shortNameForImageName:name2];
 | |
|             return [shortName1 caseInsensitiveCompare:shortName2];
 | |
|         }];
 | |
| 
 | |
|         // Cache image display names
 | |
|         _imageDisplayNames = [imageNameStrings flex_mapped:^id(NSString *path, NSUInteger idx) {
 | |
|             return [self shortNameForImageName:path];
 | |
|         }];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSString *)shortNameForImageName:(NSString *)imageName {
 | |
|     // Cache
 | |
|     NSString *shortName = _bundles_pathToShort[imageName];
 | |
|     if (shortName) {
 | |
|         return shortName;
 | |
|     }
 | |
| 
 | |
|     NSArray *components = [imageName componentsSeparatedByString:@"/"];
 | |
|     if (components.count >= 2) {
 | |
|         NSString *parentDir = components[components.count - 2];
 | |
|         if ([parentDir hasSuffix:@".framework"] || [parentDir hasSuffix:@".axbundle"]) {
 | |
|             if ([imageName hasSuffix:@".dylib"]) {
 | |
|                 shortName = imageName.lastPathComponent;
 | |
|             } else {
 | |
|                 shortName = parentDir;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!shortName) {
 | |
|         shortName = imageName.lastPathComponent;
 | |
|     }
 | |
| 
 | |
|     _bundles_pathToShort[imageName] = shortName;
 | |
|     _bundles_shortToPath[shortName] = imageName;
 | |
|     return shortName;
 | |
| }
 | |
| 
 | |
| - (NSString *)imageNameForShortName:(NSString *)imageName {
 | |
|     return _bundles_shortToPath[imageName];
 | |
| }
 | |
| 
 | |
| - (NSMutableArray<NSString *> *)classNamesInImageAtPath:(NSString *)path {
 | |
|     // Check cache
 | |
|     NSMutableArray *classNameStrings = [_bundles_pathToClassNames objectForKey:path];
 | |
|     if (classNameStrings) {
 | |
|         return classNameStrings.mutableCopy;
 | |
|     }
 | |
| 
 | |
|     unsigned int classCount = 0;
 | |
|     const char **classNames = objc_copyClassNamesForImage(path.UTF8String, &classCount);
 | |
| 
 | |
|     if (classNames) {
 | |
|         classNameStrings = [NSMutableArray flex_forEachUpTo:classCount map:^id(NSUInteger i) {
 | |
|             return @(classNames[i]);
 | |
|         }];
 | |
| 
 | |
|         free(classNames);
 | |
| 
 | |
|         [classNameStrings sortUsingSelector:@selector(caseInsensitiveCompare:)];
 | |
|         [_bundles_pathToClassNames setObject:classNameStrings forKey:path];
 | |
| 
 | |
|         return classNameStrings.mutableCopy;
 | |
|     }
 | |
| 
 | |
|     return [NSMutableArray new];
 | |
| }
 | |
| 
 | |
| #pragma mark - Public
 | |
| 
 | |
| + (void)initializeWebKitLegacy {
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         void *handle = dlopen(
 | |
|             "/System/Library/PrivateFrameworks/WebKitLegacy.framework/WebKitLegacy",
 | |
|             RTLD_LAZY
 | |
|         );
 | |
|         void (*WebKitInitialize)(void) = dlsym(handle, "WebKitInitialize");
 | |
|         if (WebKitInitialize) {
 | |
|             NSAssert(NSThread.isMainThread,
 | |
|                 @"WebKitInitialize can only be called on the main thread"
 | |
|             );
 | |
|             WebKitInitialize();
 | |
|         }
 | |
|     });
 | |
| }
 | |
| 
 | |
| - (NSArray<Class> *)copySafeClassList {
 | |
|     unsigned int count = 0;
 | |
|     Class *classes = objc_copyClassList(&count);
 | |
|     return [NSArray flex_forEachUpTo:count map:^id(NSUInteger i) {
 | |
|         Class cls = classes[i];
 | |
|         return FLEXClassIsSafe(cls) ? cls : nil;
 | |
|     }];
 | |
| }
 | |
| 
 | |
| - (NSArray<Protocol *> *)copyProtocolList {
 | |
|     unsigned int count = 0;
 | |
|     Protocol *__unsafe_unretained *protocols = objc_copyProtocolList(&count);
 | |
|     return [NSArray arrayWithObjects:protocols count:count];
 | |
| }
 | |
| 
 | |
| - (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token {
 | |
|     if (self.imagePaths.count) {
 | |
|         TBWildcardOptions options = token.options;
 | |
|         NSString *query = token.string;
 | |
| 
 | |
|         // Optimization, avoid a loop
 | |
|         if (options == TBWildcardOptionsAny) {
 | |
|             return _imageDisplayNames;
 | |
|         }
 | |
| 
 | |
|         // No dot syntax because imageDisplayNames is only mutable internally
 | |
|         return [_imageDisplayNames flex_mapped:^id(NSString *binary, NSUInteger idx) {
 | |
| //            NSString *UIName = [self shortNameForImageName:binary];
 | |
|             return TBWildcardMap(query, binary, options);
 | |
|         }];
 | |
|     }
 | |
| 
 | |
|     return [NSMutableArray new];
 | |
| }
 | |
| 
 | |
| - (NSMutableArray<NSString *> *)bundlePathsForToken:(FLEXSearchToken *)token {
 | |
|     if (self.imagePaths.count) {
 | |
|         TBWildcardOptions options = token.options;
 | |
|         NSString *query = token.string;
 | |
| 
 | |
|         // Optimization, avoid a loop
 | |
|         if (options == TBWildcardOptionsAny) {
 | |
|             return self.imagePaths;
 | |
|         }
 | |
| 
 | |
|         return [self.imagePaths flex_mapped:^id(NSString *binary, NSUInteger idx) {
 | |
|             NSString *UIName = [self shortNameForImageName:binary];
 | |
|             // If query == UIName, -> binary
 | |
|             return TBWildcardMap_(query, UIName, binary, options);
 | |
|         }];
 | |
|     }
 | |
| 
 | |
|     return [NSMutableArray new];
 | |
| }
 | |
| 
 | |
| - (NSMutableArray<NSString *> *)classesForToken:(FLEXSearchToken *)token inBundles:(NSMutableArray<NSString *> *)bundles {
 | |
|     // Edge case where token is the class we want already; return superclasses
 | |
|     if (token.isAbsolute) {
 | |
|         if (FLEXClassIsSafe(NSClassFromString(token.string))) {
 | |
|             return [NSMutableArray arrayWithObject:token.string];
 | |
|         }
 | |
| 
 | |
|         return [NSMutableArray new];
 | |
|     }
 | |
| 
 | |
|     if (bundles.count) {
 | |
|         // Get class names, remove unsafe classes
 | |
|         NSMutableArray<NSString *> *names = [self _classesForToken:token inBundles:bundles];
 | |
|         return [names flex_mapped:^NSString *(NSString *name, NSUInteger idx) {
 | |
|             Class cls = NSClassFromString(name);
 | |
|             BOOL safe = FLEXClassIsSafe(cls);
 | |
|             return safe ? name : nil;
 | |
|         }];
 | |
|     }
 | |
| 
 | |
|     return [NSMutableArray new];
 | |
| }
 | |
| 
 | |
| - (NSMutableArray<NSString *> *)_classesForToken:(FLEXSearchToken *)token inBundles:(NSMutableArray<NSString *> *)bundles {
 | |
|     TBWildcardOptions options = token.options;
 | |
|     NSString *query = token.string;
 | |
| 
 | |
|     // Optimization, avoid unnecessary sorting
 | |
|     if (bundles.count == 1) {
 | |
|         // Optimization, avoid a loop
 | |
|         if (options == TBWildcardOptionsAny) {
 | |
|             return [self classNamesInImageAtPath:bundles.firstObject];
 | |
|         }
 | |
| 
 | |
|         return [[self classNamesInImageAtPath:bundles.firstObject] flex_mapped:^id(NSString *className, NSUInteger idx) {
 | |
|             return TBWildcardMap(query, className, options);
 | |
|         }];
 | |
|     }
 | |
|     else {
 | |
|         // Optimization, avoid a loop
 | |
|         if (options == TBWildcardOptionsAny) {
 | |
|             return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
 | |
|                 return [self classNamesInImageAtPath:bundlePath];
 | |
|             }] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
 | |
|         }
 | |
| 
 | |
|         return [[bundles flex_flatmapped:^NSArray *(NSString *bundlePath, NSUInteger idx) {
 | |
|             return [[self classNamesInImageAtPath:bundlePath] flex_mapped:^id(NSString *className, NSUInteger idx) {
 | |
|                 return TBWildcardMap(query, className, options);
 | |
|             }];
 | |
|         }] flex_sortedUsingSelector:@selector(caseInsensitiveCompare:)];
 | |
|     }
 | |
| }
 | |
| 
 | |
| - (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token
 | |
|                                                     instance:(NSNumber *)checkInstance
 | |
|                                                    inClasses:(NSArray<NSString *> *)classes {
 | |
|     if (classes.count) {
 | |
|         TBWildcardOptions options = token.options;
 | |
|         BOOL instance = checkInstance.boolValue;
 | |
|         NSString *selector = token.string;
 | |
| 
 | |
|         switch (options) {
 | |
|             // In practice I don't think this case is ever used with methods,
 | |
|             // since they will always have a suffix wildcard at the end
 | |
|             case TBWildcardOptionsNone: {
 | |
|                 SEL sel = (SEL)selector.UTF8String;
 | |
|                 return @[[classes flex_mapped:^id(NSString *name, NSUInteger idx) {
 | |
|                     Class cls = NSClassFromString(name);
 | |
|                     // Use metaclass if not instance
 | |
|                     if (!instance) {
 | |
|                         cls = object_getClass(cls);
 | |
|                     }
 | |
|                     
 | |
|                     // Method is absolute
 | |
|                     return [FLEXMethod selector:sel class:cls];
 | |
|                 }]];
 | |
|             }
 | |
|             case TBWildcardOptionsAny: {
 | |
|                 return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
 | |
|                     // Any means `instance` was not specified
 | |
|                     Class cls = NSClassFromString(name);
 | |
|                     return [cls flex_allMethods];
 | |
|                 }];
 | |
|             }
 | |
|             default: {
 | |
|                 // Only "if contains"
 | |
|                 if (options & TBWildcardOptionsPrefix &&
 | |
|                     options & TBWildcardOptionsSuffix) {
 | |
|                     return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
 | |
|                         Class cls = NSClassFromString(name);
 | |
|                         return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
 | |
| 
 | |
|                             // Method is a prefix-suffix wildcard
 | |
|                             if (Contains(method.selectorString, selector)) {
 | |
|                                 return method;
 | |
|                             }
 | |
|                             return nil;
 | |
|                         }];
 | |
|                     }];
 | |
|                 }
 | |
|                 // Only "if method ends with with selector"
 | |
|                 else if (options & TBWildcardOptionsPrefix) {
 | |
|                     return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
 | |
|                         Class cls = NSClassFromString(name);
 | |
| 
 | |
|                         return [[cls flex_allMethods] flex_mapped:^id(FLEXMethod *method, NSUInteger idx) {
 | |
|                             // Method is a prefix wildcard
 | |
|                             if (HasSuffix(method.selectorString, selector)) {
 | |
|                                 return method;
 | |
|                             }
 | |
|                             return nil;
 | |
|                         }];
 | |
|                     }];
 | |
|                 }
 | |
|                 // Only "if method starts with with selector"
 | |
|                 else if (options & TBWildcardOptionsSuffix) {
 | |
|                     assert(checkInstance);
 | |
| 
 | |
|                     return [classes flex_mapped:^NSArray *(NSString *name, NSUInteger idx) {
 | |
|                         Class cls = NSClassFromString(name);
 | |
| 
 | |
|                         // Case like "Bundle.class.-" where we want "-" to match anything
 | |
|                         if (!selector.length) {
 | |
|                             if (instance) {
 | |
|                                 return [cls flex_allInstanceMethods];
 | |
|                             } else {
 | |
|                                 return [cls flex_allClassMethods];
 | |
|                             }
 | |
|                         }
 | |
| 
 | |
|                         id mapping = ^id(FLEXMethod *method) {
 | |
|                             // Method is a suffix wildcard
 | |
|                             if (HasPrefix(method.selectorString, selector)) {
 | |
|                                 return method;
 | |
|                             }
 | |
|                             return nil;
 | |
|                         };
 | |
| 
 | |
|                         if (instance) {
 | |
|                             return [[cls flex_allInstanceMethods] flex_mapped:mapping];
 | |
|                         } else {
 | |
|                             return [[cls flex_allClassMethods] flex_mapped:mapping];
 | |
|                         }
 | |
|                     }];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return [NSMutableArray new];
 | |
| }
 | |
| 
 | |
| @end
 | 
