mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-31 04:44:14 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		| @@ -0,0 +1,54 @@ | ||||
| // | ||||
| //  FLEXRuntimeClient.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/22/17. | ||||
| //  Copyright © 2017 Tanner Bennett. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXSearchToken.h" | ||||
| @class FLEXMethod; | ||||
|  | ||||
| /// Accepts runtime queries given a token. | ||||
| @interface FLEXRuntimeClient : NSObject | ||||
|  | ||||
| @property (nonatomic, readonly, class) FLEXRuntimeClient *runtime; | ||||
|  | ||||
| /// Called automatically when \c FLEXRuntime is first used. | ||||
| /// You may call it again when you think a library has | ||||
| /// been loaded since this method was first called. | ||||
| - (void)reloadLibrariesList; | ||||
|  | ||||
| /// You must call this method on the main thread | ||||
| /// before you attempt to call \c copySafeClassList. | ||||
| + (void)initializeWebKitLegacy; | ||||
|  | ||||
| /// Do not call unless you absolutely need all classes. This will cause | ||||
| /// every class in the runtime to initialize itself, which is not common. | ||||
| /// Before you call this method, call \c initializeWebKitLegacy on the main thread. | ||||
| - (NSArray<Class> *)copySafeClassList; | ||||
|  | ||||
| - (NSArray<Protocol *> *)copyProtocolList; | ||||
|  | ||||
| /// An array of strings representing the currently loaded libraries. | ||||
| @property (nonatomic, readonly) NSArray<NSString *> *imageDisplayNames; | ||||
|  | ||||
| /// "Image name" is the path of the bundle | ||||
| - (NSString *)shortNameForImageName:(NSString *)imageName; | ||||
| /// "Image name" is the path of the bundle | ||||
| - (NSString *)imageNameForShortName:(NSString *)imageName; | ||||
|  | ||||
| /// @return Bundle names for the UI | ||||
| - (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token; | ||||
| /// @return Bundle paths for more queries | ||||
| - (NSMutableArray<NSString *> *)bundlePathsForToken:(FLEXSearchToken *)token; | ||||
| /// @return Class names | ||||
| - (NSMutableArray<NSString *> *)classesForToken:(FLEXSearchToken *)token | ||||
|                                       inBundles:(NSMutableArray<NSString *> *)bundlePaths; | ||||
| /// @return A list of lists of \c FLEXMethods where | ||||
| /// each list corresponds to one of the given classes | ||||
| - (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token | ||||
|                                                     instance:(NSNumber *)onlyInstanceMethods | ||||
|                                                    inClasses:(NSArray<NSString *> *)classes; | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,416 @@ | ||||
| // | ||||
| //  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 | ||||
| @@ -0,0 +1,36 @@ | ||||
| // | ||||
| //  FLEXRuntimeController.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/23/17. | ||||
| //  Copyright © 2017 Tanner Bennett. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeKeyPath.h" | ||||
|  | ||||
| /// Wraps FLEXRuntimeClient and provides extra caching mechanisms | ||||
| @interface FLEXRuntimeController : NSObject | ||||
|  | ||||
| /// @return An array of strings if the key path only evaluates | ||||
| ///         to a class or bundle; otherwise, a list of lists of FLEXMethods. | ||||
| + (NSArray *)dataForKeyPath:(FLEXRuntimeKeyPath *)keyPath; | ||||
|  | ||||
| /// Useful when you need to specify which classes to search in. | ||||
| /// \c dataForKeyPath: will only search classes matching the class key. | ||||
| /// We use this elsewhere when we need to search a class hierarchy. | ||||
| + (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token | ||||
|                                              instance:(NSNumber *)onlyInstanceMethods | ||||
|                                             inClasses:(NSArray<NSString*> *)classes; | ||||
|  | ||||
| /// Useful when you need the classes that are associated with the | ||||
| /// double list of methods returned from \c dataForKeyPath | ||||
| + (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath; | ||||
|  | ||||
| + (NSString *)shortBundleNameForClass:(NSString *)name; | ||||
|  | ||||
| + (NSString *)imagePathWithShortName:(NSString *)suffix; | ||||
|  | ||||
| /// Gives back short names. For example, "Foundation.framework" | ||||
| + (NSArray<NSString*> *)allBundleNames; | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,192 @@ | ||||
| // | ||||
| //  FLEXRuntimeController.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/23/17. | ||||
| //  Copyright © 2017 Tanner Bennett. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeController.h" | ||||
| #import "FLEXRuntimeClient.h" | ||||
| #import "FLEXMethod.h" | ||||
|  | ||||
| @interface FLEXRuntimeController () | ||||
| @property (nonatomic, readonly) NSCache *bundlePathsCache; | ||||
| @property (nonatomic, readonly) NSCache *bundleNamesCache; | ||||
| @property (nonatomic, readonly) NSCache *classNamesCache; | ||||
| @property (nonatomic, readonly) NSCache *methodsCache; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXRuntimeController | ||||
|  | ||||
| #pragma mark Initialization | ||||
|  | ||||
| static FLEXRuntimeController *controller = nil; | ||||
| + (instancetype)shared { | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         controller = [self new]; | ||||
|     }); | ||||
|  | ||||
|     return controller; | ||||
| } | ||||
|  | ||||
| - (id)init { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _bundlePathsCache = [NSCache new]; | ||||
|         _bundleNamesCache = [NSCache new]; | ||||
|         _classNamesCache  = [NSCache new]; | ||||
|         _methodsCache     = [NSCache new]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark Public | ||||
|  | ||||
| + (NSArray *)dataForKeyPath:(FLEXRuntimeKeyPath *)keyPath { | ||||
|     if (keyPath.bundleKey) { | ||||
|         if (keyPath.classKey) { | ||||
|             if (keyPath.methodKey) { | ||||
|                 return [[self shared] methodsForKeyPath:keyPath]; | ||||
|             } else { | ||||
|                 return [[self shared] classesForKeyPath:keyPath]; | ||||
|             } | ||||
|         } else { | ||||
|             return [[self shared] bundleNamesForToken:keyPath.bundleKey]; | ||||
|         } | ||||
|     } else { | ||||
|         return @[]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| + (NSArray<NSArray<FLEXMethod *> *> *)methodsForToken:(FLEXSearchToken *)token | ||||
|                          instance:(NSNumber *)inst | ||||
|                         inClasses:(NSArray<NSString*> *)classes { | ||||
|     return [FLEXRuntimeClient.runtime | ||||
|         methodsForToken:token | ||||
|         instance:inst | ||||
|         inClasses:classes | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| + (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath { | ||||
|     return [[self shared] classesForKeyPath:keyPath]; | ||||
| } | ||||
|  | ||||
| + (NSString *)shortBundleNameForClass:(NSString *)name { | ||||
|     const char *imageName = class_getImageName(NSClassFromString(name)); | ||||
|     if (!imageName) { | ||||
|         return @"(unspecified)"; | ||||
|     } | ||||
|      | ||||
|     return [FLEXRuntimeClient.runtime shortNameForImageName:@(imageName)]; | ||||
| } | ||||
|  | ||||
| + (NSString *)imagePathWithShortName:(NSString *)suffix { | ||||
|     return [FLEXRuntimeClient.runtime imageNameForShortName:suffix]; | ||||
| } | ||||
|  | ||||
| + (NSArray *)allBundleNames { | ||||
|     return FLEXRuntimeClient.runtime.imageDisplayNames; | ||||
| } | ||||
|  | ||||
| #pragma mark Private | ||||
|  | ||||
| - (NSMutableArray *)bundlePathsForToken:(FLEXSearchToken *)token { | ||||
|     // Only cache if no wildcard | ||||
|     BOOL shouldCache = token == TBWildcardOptionsNone; | ||||
|  | ||||
|     if (shouldCache) { | ||||
|         NSMutableArray<NSString*> *cached = [self.bundlePathsCache objectForKey:token]; | ||||
|         if (cached) { | ||||
|             return cached; | ||||
|         } | ||||
|  | ||||
|         NSMutableArray<NSString*> *bundles = [FLEXRuntimeClient.runtime bundlePathsForToken:token]; | ||||
|         [self.bundlePathsCache setObject:bundles forKey:token]; | ||||
|         return bundles; | ||||
|     } | ||||
|     else { | ||||
|         return [FLEXRuntimeClient.runtime bundlePathsForToken:token]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSMutableArray<NSString *> *)bundleNamesForToken:(FLEXSearchToken *)token { | ||||
|     // Only cache if no wildcard | ||||
|     BOOL shouldCache = token == TBWildcardOptionsNone; | ||||
|  | ||||
|     if (shouldCache) { | ||||
|         NSMutableArray<NSString*> *cached = [self.bundleNamesCache objectForKey:token]; | ||||
|         if (cached) { | ||||
|             return cached; | ||||
|         } | ||||
|  | ||||
|         NSMutableArray<NSString*> *bundles = [FLEXRuntimeClient.runtime bundleNamesForToken:token]; | ||||
|         [self.bundleNamesCache setObject:bundles forKey:token]; | ||||
|         return bundles; | ||||
|     } | ||||
|     else { | ||||
|         return [FLEXRuntimeClient.runtime bundleNamesForToken:token]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSMutableArray<NSString *> *)classesForKeyPath:(FLEXRuntimeKeyPath *)keyPath { | ||||
|     FLEXSearchToken *classToken = keyPath.classKey; | ||||
|     FLEXSearchToken *bundleToken = keyPath.bundleKey; | ||||
|      | ||||
|     // Only cache if no wildcard | ||||
|     BOOL shouldCache = bundleToken.options == 0 && classToken.options == 0; | ||||
|     NSString *key = nil; | ||||
|  | ||||
|     if (shouldCache) { | ||||
|         key = [@[bundleToken.description, classToken.description] componentsJoinedByString:@"+"]; | ||||
|         NSMutableArray<NSString *> *cached = [self.classNamesCache objectForKey:key]; | ||||
|         if (cached) { | ||||
|             return cached; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     NSMutableArray<NSString *> *bundles = [self bundlePathsForToken:bundleToken]; | ||||
|     NSMutableArray<NSString *> *classes = [FLEXRuntimeClient.runtime | ||||
|         classesForToken:classToken inBundles:bundles | ||||
|     ]; | ||||
|  | ||||
|     if (shouldCache) { | ||||
|         [self.classNamesCache setObject:classes forKey:key]; | ||||
|     } | ||||
|  | ||||
|     return classes; | ||||
| } | ||||
|  | ||||
| - (NSArray<NSMutableArray<FLEXMethod *> *> *)methodsForKeyPath:(FLEXRuntimeKeyPath *)keyPath { | ||||
|     // Only cache if no wildcard, but check cache anyway bc I'm lazy | ||||
|     NSArray<NSMutableArray *> *cached = [self.methodsCache objectForKey:keyPath]; | ||||
|     if (cached) { | ||||
|         return cached; | ||||
|     } | ||||
|  | ||||
|     NSArray<NSString *> *classes = [self classesForKeyPath:keyPath]; | ||||
|     NSArray<NSMutableArray<FLEXMethod *> *> *methodLists = [FLEXRuntimeClient.runtime | ||||
|         methodsForToken:keyPath.methodKey | ||||
|         instance:keyPath.instanceMethods | ||||
|         inClasses:classes | ||||
|     ]; | ||||
|  | ||||
|     for (NSMutableArray<FLEXMethod *> *methods in methodLists) { | ||||
|         [methods sortUsingComparator:^NSComparisonResult(FLEXMethod *m1, FLEXMethod *m2) { | ||||
|             return [m1.description caseInsensitiveCompare:m2.description]; | ||||
|         }]; | ||||
|     } | ||||
|  | ||||
|     // Only cache if no wildcard, otherwise the cache could grow very large | ||||
|     if (keyPath.bundleKey.isAbsolute && | ||||
|         keyPath.classKey.isAbsolute) { | ||||
|         [self.methodsCache setObject:methodLists forKey:keyPath]; | ||||
|     } | ||||
|  | ||||
|     return methodLists; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,29 @@ | ||||
| // | ||||
| //  FLEXRuntimeExporter.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 3/26/20. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| /// A class for exporting all runtime metadata to an SQLite database. | ||||
| //API_AVAILABLE(ios(10.0)) | ||||
| @interface FLEXRuntimeExporter : NSObject | ||||
|  | ||||
| + (void)createRuntimeDatabaseAtPath:(NSString *)path | ||||
|                     progressHandler:(void(^)(NSString *status))progress | ||||
|                          completion:(void(^)(NSString *_Nullable error))completion; | ||||
|  | ||||
| + (void)createRuntimeDatabaseAtPath:(NSString *)path | ||||
|                           forImages:(nullable NSArray<NSString *> *)images | ||||
|                     progressHandler:(void(^)(NSString *status))progress | ||||
|                          completion:(void(^)(NSString *_Nullable error))completion; | ||||
|  | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
| @@ -0,0 +1,875 @@ | ||||
| // | ||||
| //  FLEXRuntimeExporter.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 3/26/20. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeExporter.h" | ||||
| #import "FLEXSQLiteDatabaseManager.h" | ||||
| #import "NSObject+FLEX_Reflection.h" | ||||
| #import "FLEXRuntimeController.h" | ||||
| #import "FLEXRuntimeClient.h" | ||||
| #import "NSArray+FLEX.h" | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
| #import <sqlite3.h> | ||||
|  | ||||
| #import "FLEXProtocol.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXIvar.h" | ||||
| #import "FLEXMethodBase.h" | ||||
| #import "FLEXMethod.h" | ||||
| #import "FLEXPropertyAttributes.h" | ||||
|  | ||||
| NSString * const kFREEnableForeignKeys = @"PRAGMA foreign_keys = ON;"; | ||||
|  | ||||
| /// Loaded images | ||||
| NSString * const kFRECreateTableMachOCommand = @"CREATE TABLE MachO( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "shortName TEXT, " | ||||
|     "imagePath TEXT, " | ||||
|     "bundleID TEXT " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertImage = @"INSERT INTO MachO ( " | ||||
|     "shortName, imagePath, bundleID " | ||||
| ") VALUES ( " | ||||
|     "$shortName, $imagePath, $bundleID " | ||||
| ");"; | ||||
|  | ||||
| /// Objc classes | ||||
| NSString * const kFRECreateTableClassCommand = @"CREATE TABLE Class( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "className TEXT, " | ||||
|     "superclass INTEGER, " | ||||
|     "instanceSize INTEGER, " | ||||
|     "version INTEGER, " | ||||
|     "image INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(superclass) REFERENCES Class(id), " | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertClass = @"INSERT INTO Class ( " | ||||
|     "className, instanceSize, version, image " | ||||
| ") VALUES ( " | ||||
|     "$className, $instanceSize, $version, $image " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREUpdateClassSetSuper = @"UPDATE Class SET superclass = $super WHERE id = $id;"; | ||||
|  | ||||
| /// Unique objc selectors | ||||
| NSString * const kFRECreateTableSelectorCommand = @"CREATE TABLE Selector( " | ||||
|     "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " | ||||
|     "name text NOT NULL UNIQUE " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertSelector = @"INSERT OR IGNORE INTO Selector (name) VALUES ($name);"; | ||||
|  | ||||
| /// Unique objc type encodings | ||||
| NSString * const kFRECreateTableTypeEncodingCommand = @"CREATE TABLE TypeEncoding( " | ||||
|     "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " | ||||
|     "string text NOT NULL UNIQUE, " | ||||
|     "size integer " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertTypeEncoding = @"INSERT OR IGNORE INTO TypeEncoding " | ||||
|     "(string, size) VALUES ($type, $size);"; | ||||
|  | ||||
| /// Unique objc type signatures | ||||
| NSString * const kFRECreateTableTypeSignatureCommand = @"CREATE TABLE TypeSignature( " | ||||
|     "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " | ||||
|     "string text NOT NULL UNIQUE " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertTypeSignature = @"INSERT OR IGNORE INTO TypeSignature " | ||||
|     "(string) VALUES ($type);"; | ||||
|  | ||||
| NSString * const kFRECreateTableMethodSignatureCommand = @"CREATE TABLE MethodSignature( " | ||||
|     "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " | ||||
|     "typeEncoding TEXT, " | ||||
|     "argc INTEGER, " | ||||
|     "returnType INTEGER, " | ||||
|     "frameLength INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(returnType) REFERENCES TypeEncoding(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertMethodSignature = @"INSERT INTO MethodSignature ( " | ||||
|     "typeEncoding, argc, returnType, frameLength " | ||||
| ") VALUES ( " | ||||
|     "$typeEncoding, $argc, $returnType, $frameLength " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFRECreateTableMethodCommand = @"CREATE TABLE Method( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "sel INTEGER, " | ||||
|     "class INTEGER, " | ||||
|     "instance INTEGER, " // 0 if class method, 1 if instance method | ||||
|     "signature INTEGER, " | ||||
|     "image INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(sel) REFERENCES Selector(id), " | ||||
|     "FOREIGN KEY(class) REFERENCES Class(id), " | ||||
|     "FOREIGN KEY(signature) REFERENCES MethodSignature(id), " | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertMethod = @"INSERT INTO Method ( " | ||||
|     "sel, class, instance, signature, image " | ||||
| ") VALUES ( " | ||||
|     "$sel, $class, $instance, $signature, $image " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFRECreateTablePropertyCommand = @"CREATE TABLE Property( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "name TEXT, " | ||||
|     "class INTEGER, " | ||||
|     "instance INTEGER, " // 0 if class prop, 1 if instance prop | ||||
|     "image INTEGER, " | ||||
|     "attributes TEXT, " | ||||
|  | ||||
|     "customGetter INTEGER, " | ||||
|     "customSetter INTEGER, " | ||||
|  | ||||
|     "type INTEGER, " | ||||
|     "ivar TEXT, " | ||||
|     "readonly INTEGER, " | ||||
|     "copy INTEGER, " | ||||
|     "retained INTEGER, " | ||||
|     "nonatomic INTEGER, " | ||||
|     "dynamic INTEGER, " | ||||
|     "weak INTEGER, " | ||||
|     "canGC INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(class) REFERENCES Class(id), " | ||||
|     "FOREIGN KEY(customGetter) REFERENCES Selector(id), " | ||||
|     "FOREIGN KEY(customSetter) REFERENCES Selector(id), " | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertProperty = @"INSERT INTO Property ( " | ||||
|     "name, class, instance, attributes, image, " | ||||
|     "customGetter, customSetter, type, ivar, readonly, " | ||||
|     "copy, retained, nonatomic, dynamic, weak, canGC " | ||||
| ") VALUES ( " | ||||
|     "$name, $class, $instance, $attributes, $image, " | ||||
|     "$customGetter, $customSetter, $type, $ivar, $readonly, " | ||||
|     "$copy, $retained, $nonatomic, $dynamic, $weak, $canGC " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFRECreateTableIvarCommand = @"CREATE TABLE Ivar( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "name TEXT, " | ||||
|     "offset INTEGER, " | ||||
|     "type INTEGER, " | ||||
|     "class INTEGER, " | ||||
|     "image INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(type) REFERENCES TypeEncoding(id), " | ||||
|     "FOREIGN KEY(class) REFERENCES Class(id), " | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertIvar = @"INSERT INTO Ivar ( " | ||||
|     "name, offset, type, class, image " | ||||
| ") VALUES ( " | ||||
|     "$name, $offset, $type, $class, $image " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFRECreateTableProtocolCommand = @"CREATE TABLE Protocol( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "name TEXT, " | ||||
|     "image INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertProtocol = @"INSERT INTO Protocol " | ||||
|     "(name, image) VALUES ($name, $image);"; | ||||
|  | ||||
| NSString * const kFRECreateTableProtocolPropertyCommand = @"CREATE TABLE ProtocolMember( " | ||||
|     "id INTEGER PRIMARY KEY AUTOINCREMENT, " | ||||
|     "protocol INTEGER, " | ||||
|     "required INTEGER, " | ||||
|     "instance INTEGER, " // 0 if class member, 1 if instance member | ||||
|  | ||||
|     // Only of the two below is used | ||||
|     "property TEXT, " | ||||
|     "method TEXT, " | ||||
|  | ||||
|     "image INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(protocol) REFERENCES Protocol(id), " | ||||
|     "FOREIGN KEY(image) REFERENCES MachO(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertProtocolMember = @"INSERT INTO ProtocolMember ( " | ||||
|     "protocol, required, instance, property, method, image " | ||||
| ") VALUES ( " | ||||
|     "$protocol, $required, $instance, $property, $method, $image " | ||||
| ");"; | ||||
|  | ||||
| /// For protocols conforming to other protocols | ||||
| NSString * const kFRECreateTableProtocolConformanceCommand = @"CREATE TABLE ProtocolConformance( " | ||||
|     "protocol INTEGER, " | ||||
|     "conformance INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(protocol) REFERENCES Protocol(id), " | ||||
|     "FOREIGN KEY(conformance) REFERENCES Protocol(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertProtocolConformance = @"INSERT INTO ProtocolConformance " | ||||
| "(protocol, conformance) VALUES ($protocol, $conformance);"; | ||||
|  | ||||
| /// For classes conforming to protocols | ||||
| NSString * const kFRECreateTableClassConformanceCommand = @"CREATE TABLE ClassConformance( " | ||||
|     "class INTEGER, " | ||||
|     "conformance INTEGER, " | ||||
|  | ||||
|     "FOREIGN KEY(class) REFERENCES Class(id), " | ||||
|     "FOREIGN KEY(conformance) REFERENCES Protocol(id) " | ||||
| ");"; | ||||
|  | ||||
| NSString * const kFREInsertClassConformance = @"INSERT INTO ClassConformance " | ||||
| "(class, conformance) VALUES ($class, $conformance);"; | ||||
|  | ||||
| @interface FLEXRuntimeExporter () | ||||
| @property (nonatomic, readonly) FLEXSQLiteDatabaseManager *db; | ||||
| @property (nonatomic, copy) NSArray<NSString *> *loadedShortBundleNames; | ||||
| @property (nonatomic, copy) NSArray<NSString *> *loadedBundlePaths; | ||||
| @property (nonatomic, copy) NSArray<FLEXProtocol *> *protocols; | ||||
| @property (nonatomic, copy) NSArray<Class> *classes; | ||||
|  | ||||
| @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *bundlePathsToIDs; | ||||
| @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *protocolsToIDs; | ||||
| @property (nonatomic) NSMutableDictionary<Class, NSNumber *> *classesToIDs; | ||||
| @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *typeEncodingsToIDs; | ||||
| @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *methodSignaturesToIDs; | ||||
| @property (nonatomic) NSMutableDictionary<NSString *, NSNumber *> *selectorsToIDs; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXRuntimeExporter | ||||
|  | ||||
| + (NSString *)tempFilename { | ||||
|     NSString *temp = NSTemporaryDirectory(); | ||||
|     NSString *uuid = [NSUUID.UUID.UUIDString substringToIndex:8]; | ||||
|     NSString *filename = [NSString stringWithFormat:@"FLEXRuntimeDatabase-%@.db", uuid]; | ||||
|     return [temp stringByAppendingPathComponent:filename]; | ||||
| } | ||||
|  | ||||
| + (void)createRuntimeDatabaseAtPath:(NSString *)path | ||||
|                     progressHandler:(void(^)(NSString *status))progress | ||||
|                          completion:(void (^)(NSString *))completion { | ||||
|     [self createRuntimeDatabaseAtPath:path forImages:nil progressHandler:progress completion:completion]; | ||||
| } | ||||
|  | ||||
| + (void)createRuntimeDatabaseAtPath:(NSString *)path | ||||
|                           forImages:(NSArray<NSString *> *)images | ||||
|                     progressHandler:(void(^)(NSString *status))progress | ||||
|                          completion:(void(^)(NSString *_Nullable error))completion { | ||||
|     __typeof(completion) callback = ^(NSString *error) { | ||||
|         dispatch_async(dispatch_get_main_queue(), ^{ | ||||
|             completion(error); | ||||
|         }); | ||||
|     }; | ||||
|      | ||||
|     // This must be called on the main thread first | ||||
|     if (NSThread.isMainThread) { | ||||
|         [FLEXRuntimeClient initializeWebKitLegacy]; | ||||
|     } else { | ||||
|         dispatch_sync(dispatch_get_main_queue(), ^{ | ||||
|             [FLEXRuntimeClient initializeWebKitLegacy]; | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ | ||||
|         NSError *error = nil; | ||||
|         NSString *errorMessage = nil; | ||||
|          | ||||
|         // Get unused temp filename, remove existing database if any | ||||
|         NSString *tempPath = [self tempFilename]; | ||||
|         if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) { | ||||
|             [NSFileManager.defaultManager removeItemAtPath:tempPath error:&error]; | ||||
|             if (error) { | ||||
|                 callback(error.localizedDescription); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // Attempt to create and populate the database, abort if we fail | ||||
|         FLEXRuntimeExporter *exporter = [self new]; | ||||
|         exporter.loadedBundlePaths = images; | ||||
|         if (![exporter createAndPopulateDatabaseAtPath:tempPath | ||||
|                                        progressHandler:progress | ||||
|                                                  error:&errorMessage]) { | ||||
|             // Remove temp database if it was not moved | ||||
|             if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) { | ||||
|                 [NSFileManager.defaultManager removeItemAtPath:tempPath error:nil]; | ||||
|             } | ||||
|              | ||||
|             callback(errorMessage); | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         // Remove old database at given path | ||||
|         if ([NSFileManager.defaultManager fileExistsAtPath:path]) { | ||||
|             [NSFileManager.defaultManager removeItemAtPath:path error:&error]; | ||||
|             if (error) { | ||||
|                 callback(error.localizedDescription); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // Move new database to desired path | ||||
|         [NSFileManager.defaultManager moveItemAtPath:tempPath toPath:path error:&error]; | ||||
|         if (error) { | ||||
|             callback(error.localizedDescription); | ||||
|         } | ||||
|          | ||||
|         // Remove temp database if it was not moved | ||||
|         if ([NSFileManager.defaultManager fileExistsAtPath:tempPath]) { | ||||
|             [NSFileManager.defaultManager removeItemAtPath:tempPath error:nil]; | ||||
|         } | ||||
|          | ||||
|         callback(nil); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| - (id)init { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _bundlePathsToIDs = [NSMutableDictionary new]; | ||||
|         _protocolsToIDs = [NSMutableDictionary new]; | ||||
|         _classesToIDs = [NSMutableDictionary new]; | ||||
|         _typeEncodingsToIDs = [NSMutableDictionary new]; | ||||
|         _methodSignaturesToIDs = [NSMutableDictionary new]; | ||||
|         _selectorsToIDs = [NSMutableDictionary new]; | ||||
|          | ||||
|         _bundlePathsToIDs[NSNull.null] = (id)NSNull.null; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (BOOL)createAndPopulateDatabaseAtPath:(NSString *)path | ||||
|                         progressHandler:(void(^)(NSString *status))step | ||||
|                                   error:(NSString **)error { | ||||
|     _db = [FLEXSQLiteDatabaseManager managerForDatabase:path]; | ||||
|      | ||||
|     [self loadMetadata:step]; | ||||
|      | ||||
|     if ([self createTables] && [self addImages:step] && [self addProtocols:step] && | ||||
|         [self addClasses:step] && [self setSuperclasses:step] &&  | ||||
|         [self addProtocolConformances:step] && [self addClassConformances:step] && | ||||
|         [self addIvars:step] && [self addMethods:step] && [self addProperties:step]) { | ||||
|         _db = nil; // Close the database | ||||
|         return YES; | ||||
|     } | ||||
|      | ||||
|     *error = self.db.lastResult.message; | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (void)loadMetadata:(void(^)(NSString *status))progress { | ||||
|     progress(@"Loading metadata…"); | ||||
|      | ||||
|     FLEXRuntimeClient *runtime = FLEXRuntimeClient.runtime; | ||||
|      | ||||
|     // Only load metadata for the existing paths if any | ||||
|     if (self.loadedBundlePaths) { | ||||
|         // Images | ||||
|         self.loadedShortBundleNames = [self.loadedBundlePaths flex_mapped:^id(NSString *path, NSUInteger idx) { | ||||
|             return [runtime shortNameForImageName:path]; | ||||
|         }]; | ||||
|          | ||||
|         // Classes | ||||
|         self.classes = [[runtime classesForToken:FLEXSearchToken.any | ||||
|             inBundles:self.loadedBundlePaths.mutableCopy | ||||
|         ] flex_mapped:^id(NSString *cls, NSUInteger idx) { | ||||
|             return NSClassFromString(cls); | ||||
|         }]; | ||||
|     } else { | ||||
|         // Images | ||||
|         self.loadedShortBundleNames = runtime.imageDisplayNames; | ||||
|         self.loadedBundlePaths = [self.loadedShortBundleNames flex_mapped:^id(NSString *name, NSUInteger idx) { | ||||
|             return [runtime imageNameForShortName:name]; | ||||
|         }]; | ||||
|          | ||||
|         // Classes | ||||
|         self.classes = [runtime copySafeClassList]; | ||||
|     } | ||||
|      | ||||
|     // ...except protocols, because there's not a lot of them | ||||
|     // and there's no way load the protocols for a given image | ||||
|     self.protocols = [[runtime copyProtocolList] flex_mapped:^id(Protocol *proto, NSUInteger idx) { | ||||
|         return [FLEXProtocol protocol:proto]; | ||||
|     }]; | ||||
| } | ||||
|  | ||||
| - (BOOL)createTables { | ||||
|     NSArray<NSString *> *commands = @[ | ||||
|         kFREEnableForeignKeys, | ||||
|         kFRECreateTableMachOCommand, | ||||
|         kFRECreateTableClassCommand, | ||||
|         kFRECreateTableSelectorCommand, | ||||
|         kFRECreateTableTypeEncodingCommand, | ||||
|         kFRECreateTableTypeSignatureCommand, | ||||
|         kFRECreateTableMethodSignatureCommand, | ||||
|         kFRECreateTableMethodCommand, | ||||
|         kFRECreateTablePropertyCommand, | ||||
|         kFRECreateTableIvarCommand, | ||||
|         kFRECreateTableProtocolCommand, | ||||
|         kFRECreateTableProtocolPropertyCommand, | ||||
|         kFRECreateTableProtocolConformanceCommand, | ||||
|         kFRECreateTableClassConformanceCommand | ||||
|     ]; | ||||
|      | ||||
|     for (NSString *command in commands) { | ||||
|         if (![self.db executeStatement:command]) { | ||||
|             return NO; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addImages:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding loaded images…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSArray *shortNames = self.loadedShortBundleNames; | ||||
|     NSArray *fullPaths = self.loadedBundlePaths; | ||||
|     NSParameterAssert(shortNames.count == fullPaths.count); | ||||
|      | ||||
|     NSInteger count = shortNames.count; | ||||
|     for (NSInteger i = 0; i < count; i++) { | ||||
|         // Grab bundle ID | ||||
|         NSString *bundleID = [NSBundle | ||||
|             bundleWithPath:fullPaths[i] | ||||
|         ].bundleIdentifier;  | ||||
|          | ||||
|         [database executeStatement:kFREInsertImage arguments:@{ | ||||
|             @"$shortName": shortNames[i], | ||||
|             @"$imagePath": fullPaths[i], | ||||
|             @"$bundleID":  bundleID ?: NSNull.null | ||||
|         }]; | ||||
|          | ||||
|         if (database.lastResult.isError) { | ||||
|             return NO; | ||||
|         } else { | ||||
|             self.bundlePathsToIDs[fullPaths[i]] = @(database.lastRowID); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| NS_INLINE BOOL FREInsertProtocolMember(FLEXSQLiteDatabaseManager *db, | ||||
|                                        id proto, id required, id instance, | ||||
|                                        id prop, id methSel, id image) { | ||||
|     return ![db executeStatement:kFREInsertProtocolMember arguments:@{ | ||||
|         @"$protocol": proto, | ||||
|         @"$required": required, | ||||
|         @"$instance": instance ?: NSNull.null, | ||||
|         @"$property": prop ?: NSNull.null, | ||||
|         @"$method": methSel ?: NSNull.null, | ||||
|         @"$image": image | ||||
|     }].isError; | ||||
| } | ||||
|  | ||||
| - (BOOL)addProtocols:(void(^)(NSString *status))progress { | ||||
|     progress([NSString stringWithFormat:@"Adding %@ protocols…", @(self.protocols.count)]); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *imageIDs = self.bundlePathsToIDs; | ||||
|      | ||||
|     for (FLEXProtocol *proto in self.protocols) { | ||||
|         id imagePath = proto.imagePath ?: NSNull.null; | ||||
|         NSNumber *image = imageIDs[imagePath] ?: NSNull.null; | ||||
|         NSNumber *pid = nil; | ||||
|          | ||||
|         // Insert protocol | ||||
|         BOOL failed = [database executeStatement:kFREInsertProtocol arguments:@{ | ||||
|             @"$name": proto.name, @"$image": image | ||||
|         }].isError; | ||||
|          | ||||
|         // Cache rowid | ||||
|         if (failed) { | ||||
|             return NO; | ||||
|         } else { | ||||
|             self.protocolsToIDs[proto.name] = pid = @(database.lastRowID); | ||||
|         } | ||||
|          | ||||
|         // Insert its members // | ||||
|          | ||||
|         // Required methods | ||||
|         for (FLEXMethodDescription *method in proto.requiredMethods) { | ||||
|             NSString *selector = NSStringFromSelector(method.selector); | ||||
|             if (!FREInsertProtocolMember(database, pid, @YES, method.instance, nil, selector, image)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|         // Optional methods | ||||
|         for (FLEXMethodDescription *method in proto.optionalMethods) { | ||||
|             NSString *selector = NSStringFromSelector(method.selector); | ||||
|             if (!FREInsertProtocolMember(database, pid, @NO, method.instance, nil, selector, image)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if (@available(iOS 10, *)) { | ||||
|             // Required properties | ||||
|             for (FLEXProperty *property in proto.requiredProperties) { | ||||
|                 BOOL success = FREInsertProtocolMember( | ||||
|                    database, pid, @YES, @(property.isClassProperty), property.name, NSNull.null, image | ||||
|                 ); | ||||
|                  | ||||
|                 if (!success) return NO; | ||||
|             } | ||||
|             // Optional properties | ||||
|             for (FLEXProperty *property in proto.optionalProperties) { | ||||
|                 BOOL success = FREInsertProtocolMember( | ||||
|                     database, pid, @NO, @(property.isClassProperty), property.name, NSNull.null, image | ||||
|                 ); | ||||
|                  | ||||
|                 if (!success) return NO; | ||||
|             } | ||||
|         } else { | ||||
|             // Just... properties. | ||||
|             for (FLEXProperty *property in proto.properties) { | ||||
|                 BOOL success = FREInsertProtocolMember( | ||||
|                     database, pid, nil, @(property.isClassProperty), property.name, NSNull.null, image | ||||
|                 ); | ||||
|                  | ||||
|                 if (!success) return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addProtocolConformances:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding protocol-to-protocol conformances…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *protocolIDs = self.protocolsToIDs; | ||||
|      | ||||
|     for (FLEXProtocol *proto in self.protocols) { | ||||
|         id protoID = protocolIDs[proto.name]; | ||||
|          | ||||
|         for (FLEXProtocol *conform in proto.protocols) { | ||||
|             BOOL failed = [database executeStatement:kFREInsertProtocolConformance arguments:@{ | ||||
|                 @"$protocol": protoID, | ||||
|                 @"$conformance": protocolIDs[conform.name] | ||||
|             }].isError; | ||||
|              | ||||
|             if (failed) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addClasses:(void(^)(NSString *status))progress { | ||||
|     progress([NSString stringWithFormat:@"Adding %@ classes…", @(self.classes.count)]); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *imageIDs = self.bundlePathsToIDs; | ||||
|      | ||||
|     for (Class cls in self.classes) { | ||||
|         const char *imageName = class_getImageName(cls); | ||||
|         id image = imageName ? imageIDs[@(imageName)] : NSNull.null; | ||||
|         image = image ?: NSNull.null; | ||||
|          | ||||
|         BOOL failed = [database executeStatement:kFREInsertClass arguments:@{ | ||||
|             @"$className":    NSStringFromClass(cls), | ||||
|             @"$instanceSize": @(class_getInstanceSize(cls)), | ||||
|             @"$version":      @(class_getVersion(cls)), | ||||
|             @"$image":        image | ||||
|         }].isError; | ||||
|          | ||||
|         if (failed) { | ||||
|             return NO; | ||||
|         } else { | ||||
|             self.classesToIDs[(id)cls] = @(database.lastRowID); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)setSuperclasses:(void(^)(NSString *status))progress { | ||||
|     progress(@"Setting superclasses…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|      | ||||
|     for (Class cls in self.classes) { | ||||
|         // Grab superclass ID | ||||
|         Class superclass = class_getSuperclass(cls); | ||||
|         NSNumber *superclassID = _classesToIDs[class_getSuperclass(cls)]; | ||||
|          | ||||
|         // ... or add the superclass and cache its ID if the | ||||
|         // superclass does not reside in the target image(s) | ||||
|         if (!superclassID) { | ||||
|             NSDictionary *args = @{ @"$className": NSStringFromClass(superclass) }; | ||||
|             BOOL failed = [database executeStatement:kFREInsertClass arguments:args].isError; | ||||
|             if (failed) { return NO; } | ||||
|              | ||||
|             _classesToIDs[(id)superclass] = superclassID = @(database.lastRowID); | ||||
|         } | ||||
|          | ||||
|         if (superclass) { | ||||
|             BOOL failed = [database executeStatement:kFREUpdateClassSetSuper arguments:@{ | ||||
|                 @"$super": superclassID, @"$id": _classesToIDs[cls] | ||||
|             }].isError; | ||||
|              | ||||
|             if (failed) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addClassConformances:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding class-to-protocol conformances…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *protocolIDs = self.protocolsToIDs; | ||||
|     NSDictionary *classIDs = self.classesToIDs; | ||||
|      | ||||
|     for (Class cls in self.classes) { | ||||
|         id classID = classIDs[(id)cls]; | ||||
|          | ||||
|         for (FLEXProtocol *conform in FLEXGetConformedProtocols(cls)) { | ||||
|             BOOL failed = [database executeStatement:kFREInsertClassConformance arguments:@{ | ||||
|                 @"$class": classID, | ||||
|                 @"$conformance": protocolIDs[conform.name] | ||||
|             }].isError; | ||||
|              | ||||
|             if (failed) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addIvars:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding ivars…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *imageIDs = self.bundlePathsToIDs; | ||||
|      | ||||
|     for (Class cls in self.classes) { | ||||
|         for (FLEXIvar *ivar in FLEXGetAllIvars(cls)) { | ||||
|             // Insert type first | ||||
|             if (![self addTypeEncoding:ivar.typeEncoding size:ivar.size]) { | ||||
|                 return NO; | ||||
|             } | ||||
|              | ||||
|             id imagePath = ivar.imagePath ?: NSNull.null; | ||||
|             NSNumber *image = imageIDs[imagePath] ?: NSNull.null; | ||||
|              | ||||
|             BOOL failed = [database executeStatement:kFREInsertIvar arguments:@{ | ||||
|                 @"$name":   ivar.name, | ||||
|                 @"$offset": @(ivar.offset), | ||||
|                 @"$type":   _typeEncodingsToIDs[ivar.typeEncoding], | ||||
|                 @"$class":  _classesToIDs[cls], | ||||
|                 @"$image":  image | ||||
|             }].isError; | ||||
|              | ||||
|             if (failed) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addMethods:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding methods…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *imageIDs = self.bundlePathsToIDs; | ||||
|      | ||||
|     // Loop over all classes | ||||
|     for (Class cls in self.classes) { | ||||
|         NSNumber *classID = _classesToIDs[(id)cls]; | ||||
|         const char *imageName = class_getImageName(cls); | ||||
|         id image = imageName ? imageIDs[@(imageName)] : NSNull.null; | ||||
|         image = image ?: NSNull.null; | ||||
|          | ||||
|         // Block used to process each message | ||||
|         BOOL (^insert)(FLEXMethod *, NSNumber *) = ^BOOL(FLEXMethod *method, NSNumber *instance) { | ||||
|             // Insert selector and signature first | ||||
|             if (![self addSelector:method.selectorString]) { | ||||
|                 return NO; | ||||
|             } | ||||
|             if (![self addMethodSignature:method]) { | ||||
|                 return NO; | ||||
|             } | ||||
|              | ||||
|             return ![database executeStatement:kFREInsertMethod arguments:@{ | ||||
|                 @"$sel":       self->_selectorsToIDs[method.selectorString], | ||||
|                 @"$class":     classID, | ||||
|                 @"$instance":  instance, | ||||
|                 @"$signature": self->_methodSignaturesToIDs[method.signatureString], | ||||
|                 @"$image":     image | ||||
|             }].isError; | ||||
|         }; | ||||
|          | ||||
|         // Loop over all instance and class methods of that class // | ||||
|          | ||||
|         for (FLEXMethod *method in FLEXGetAllMethods(cls, YES)) { | ||||
|             if (!insert(method, @YES)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|         for (FLEXMethod *method in FLEXGetAllMethods(object_getClass(cls), NO)) { | ||||
|             if (!insert(method, @NO)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addProperties:(void(^)(NSString *status))progress { | ||||
|     progress(@"Adding properties…"); | ||||
|      | ||||
|     FLEXSQLiteDatabaseManager *database = self.db; | ||||
|     NSDictionary *imageIDs = self.bundlePathsToIDs; | ||||
|      | ||||
|     // Loop over all classes | ||||
|     for (Class cls in self.classes) { | ||||
|         NSNumber *classID = _classesToIDs[(id)cls]; | ||||
|          | ||||
|         // Block used to process each message | ||||
|         BOOL (^insert)(FLEXProperty *, NSNumber *) = ^BOOL(FLEXProperty *property, NSNumber *instance) { | ||||
|             FLEXPropertyAttributes *attrs = property.attributes; | ||||
|             NSString *customGetter = attrs.customGetterString; | ||||
|             NSString *customSetter = attrs.customSetterString; | ||||
|              | ||||
|             // Insert selectors first | ||||
|             if (customGetter) { | ||||
|                 if (![self addSelector:customGetter]) { | ||||
|                     return NO; | ||||
|                 } | ||||
|             } | ||||
|             if (customSetter) { | ||||
|                 if (![self addSelector:customSetter]) { | ||||
|                     return NO; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Insert type encoding first | ||||
|             NSInteger size = [FLEXTypeEncodingParser | ||||
|                 sizeForTypeEncoding:attrs.typeEncoding alignment:nil | ||||
|             ]; | ||||
|             if (![self addTypeEncoding:attrs.typeEncoding size:size]) { | ||||
|                 return NO; | ||||
|             } | ||||
|              | ||||
|             id imagePath = property.imagePath ?: NSNull.null; | ||||
|             id image = imageIDs[imagePath] ?: NSNull.null; | ||||
|             return ![database executeStatement:kFREInsertProperty arguments:@{ | ||||
|                 @"$name":       property.name, | ||||
|                 @"$class":      classID, | ||||
|                 @"$instance":   instance, | ||||
|                 @"$image":      image, | ||||
|                 @"$attributes": attrs.string, | ||||
|                  | ||||
|                 @"$customGetter": self->_selectorsToIDs[customGetter] ?: NSNull.null, | ||||
|                 @"$customSetter": self->_selectorsToIDs[customSetter] ?: NSNull.null, | ||||
|                  | ||||
|                 @"$type":      self->_typeEncodingsToIDs[attrs.typeEncoding] ?: NSNull.null, | ||||
|                 @"$ivar":      attrs.backingIvar ?: NSNull.null, | ||||
|                 @"$readonly":  @(attrs.isReadOnly), | ||||
|                 @"$copy":      @(attrs.isCopy), | ||||
|                 @"$retained":  @(attrs.isRetained), | ||||
|                 @"$nonatomic": @(attrs.isNonatomic), | ||||
|                 @"$dynamic":   @(attrs.isDynamic), | ||||
|                 @"$weak":      @(attrs.isWeak), | ||||
|                 @"$canGC":     @(attrs.isGarbageCollectable), | ||||
|             }].isError; | ||||
|         }; | ||||
|          | ||||
|         // Loop over all instance and class methods of that class // | ||||
|          | ||||
|         for (FLEXProperty *property in FLEXGetAllProperties(cls)) { | ||||
|             if (!insert(property, @YES)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|         for (FLEXProperty *property in FLEXGetAllProperties(object_getClass(cls))) { | ||||
|             if (!insert(property, @NO)) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (BOOL)addSelector:(NSString *)sel { | ||||
|     return [self executeInsert:kFREInsertSelector args:@{ | ||||
|         @"$name": sel | ||||
|     } key:sel cacheResult:_selectorsToIDs]; | ||||
| } | ||||
|  | ||||
| - (BOOL)addTypeEncoding:(NSString *)type size:(NSInteger)size { | ||||
|     return [self executeInsert:kFREInsertTypeEncoding args:@{ | ||||
|         @"$type": type, @"$size": @(size) | ||||
|     } key:type cacheResult:_typeEncodingsToIDs]; | ||||
| } | ||||
|  | ||||
| - (BOOL)addMethodSignature:(FLEXMethod *)method { | ||||
|     NSString *signature = method.signatureString; | ||||
|     NSString *returnType = @((char *)method.returnType); | ||||
|      | ||||
|     // Insert return type first | ||||
|     if (![self addTypeEncoding:returnType size:method.returnSize]) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     return [self executeInsert:kFREInsertMethodSignature args:@{ | ||||
|         @"$typeEncoding": signature, | ||||
|         @"$returnType":   _typeEncodingsToIDs[returnType], | ||||
|         @"$argc":         @(method.numberOfArguments), | ||||
|         @"$frameLength":  @(method.signature.frameLength) | ||||
|     } key:signature cacheResult:_methodSignaturesToIDs]; | ||||
| } | ||||
|  | ||||
| - (BOOL)executeInsert:(NSString *)statement | ||||
|                  args:(NSDictionary *)args | ||||
|                   key:(NSString *)cacheKey | ||||
|           cacheResult:(NSMutableDictionary<NSString *, NSNumber *> *)rowids { | ||||
|     // Check if already inserted | ||||
|     if (rowids[cacheKey]) { | ||||
|         return YES; | ||||
|     } | ||||
|      | ||||
|     // Insert | ||||
|     FLEXSQLiteDatabaseManager *database = _db; | ||||
|     [database executeStatement:statement arguments:args]; | ||||
|      | ||||
|     if (database.lastResult.isError) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     // Cache rowid | ||||
|     rowids[cacheKey] = @(database.lastRowID); | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| @end | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn