This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at master 11 kB View raw
1// Redirects MLKit's NSBundle URLForResource:withExtension: lookups for its 2// resource bundles (MLKitPoseDetectionAccurateResources, 3// MLKitPoseDetectionCommonResources, MLKitXenoResources) to a directory the 4// library's Kotlin init code populates at runtime. Without this, MLKit's 5// `.tflite` / `.binarypb` weights have to be copied into the consumer's app 6// bundle via an Xcode build phase forcing per-app setup. 7// 8// Kotlin side calls mlkit_set_resource_dir() with a Caches-directory path 9// after extracting the resource files there. The swizzled URLForResource 10// method first defers to the original (so non-MLKit lookups are unaffected), 11// then falls back to the registered directory for the specific bundle names 12// MLKit queries. 13 14#import <Foundation/Foundation.h> 15#import <objc/runtime.h> 16 17static NSString *_mlkitResourceDir = nil; 18 19// Install MLKit telemetry stubs. Safe to call multiple times. 20// Runs at two points: during +load (MLKit classes may not be registered yet, 21// in which case we silently skip and retry), and from mlkit_set_resource_dir 22// (called by Kotlin after app init all classes definitely loaded by then). 23// Each stub has its own "installed" flag so partial success on the first 24// attempt still leaves work for the second. 25static void _pd_mlkit_noop_log(id, SEL, id, id); 26static void _pd_mlkit_noop_writelog(id, SEL, id, id, id, id, id, id, id); 27static id _pd_mlkit_compute_url(Class, SEL, id, id, id); 28static void _pd_mlkit_noop_start_auto_upload(id, SEL); 29static void _pd_mlkit_noop_flush_upload(id, SEL, id, BOOL); 30static void _pd_mlkit_noop_log_counters(id, SEL, id); 31 32static BOOL _pd_logger_stubbed = NO; 33static BOOL _pd_writer_stubbed = NO; 34static BOOL _pd_fileutil_stubbed = NO; 35static BOOL _pd_uploader_start_stubbed = NO; 36static BOOL _pd_uploader_flush_stubbed = NO; 37static BOOL _pd_metalogger_stubbed = NO; 38 39static void _pd_install_clearcut_stub(void) { 40 // 1. -[MLKITx_CCTClearcutLogger log:completion:] (high-level entry) 41 if (!_pd_logger_stubbed) { 42 Class logger = NSClassFromString(@"MLKITx_CCTClearcutLogger"); 43 if (logger) { 44 Method m = class_getInstanceMethod(logger, 45 @selector(log:completion:)); 46 if (m) { 47 method_setImplementation(m, (IMP)_pd_mlkit_noop_log); 48 _pd_logger_stubbed = YES; 49 } 50 } 51 } 52 // 2. -[MLKITx_CCTLogWriter writeLog:pseudonymousID:logDirectory:clock: 53 // logTransformers:completionQueue:completion:] 54 // Some callers bypass the Logger and hit the LogWriter directly 55 // which is the path kima's crash stack showed. 56 if (!_pd_writer_stubbed) { 57 Class writer = NSClassFromString(@"MLKITx_CCTLogWriter"); 58 if (writer) { 59 Method m = class_getInstanceMethod(writer, 60 @selector(writeLog:pseudonymousID:logDirectory:clock:logTransformers:completionQueue:completion:)); 61 if (m) { 62 method_setImplementation(m, (IMP)_pd_mlkit_noop_writelog); 63 _pd_writer_stubbed = YES; 64 } 65 } 66 } 67 // 3. Fallback: if neither stub fires and something reaches 68 // +[MLKITx_CCTClearcutFileUtility computeUrlForLogContextDir:context: 69 // bundleId:], make that class method respond instead of throwing. 70 if (!_pd_fileutil_stubbed) { 71 Class fileutil = NSClassFromString(@"MLKITx_CCTClearcutFileUtility"); 72 if (fileutil) { 73 SEL sel = @selector(computeUrlForLogContextDir:context:bundleId:); 74 // Try to override the existing method impl if there IS one; if 75 // not (this is what causes the original crash the class method 76 // table has lost the entry), add it via the metaclass. 77 Method existing = class_getClassMethod(fileutil, sel); 78 if (existing) { 79 method_setImplementation(existing, (IMP)_pd_mlkit_compute_url); 80 _pd_fileutil_stubbed = YES; 81 } else { 82 Class meta = object_getClass((id)fileutil); 83 if (meta && class_addMethod(meta, sel, 84 (IMP)_pd_mlkit_compute_url, 85 "@@:@@@")) { 86 _pd_fileutil_stubbed = YES; 87 } 88 } 89 } 90 } 91 // 4. Auto-upload path (separate from writeLog fires on a periodic 92 // timer, not per pose detection). Stub at three levels so any call 93 // into the chain gets neutralized before reaching the broken 94 // logCounters / flushCounters code. 95 Class uploader = NSClassFromString(@"MLKITx_CCTClearcutUploader"); 96 if (uploader) { 97 if (!_pd_uploader_start_stubbed) { 98 Method m = class_getInstanceMethod(uploader, 99 @selector(startAutoUpload)); 100 if (m) { 101 method_setImplementation(m, (IMP)_pd_mlkit_noop_start_auto_upload); 102 _pd_uploader_start_stubbed = YES; 103 } 104 } 105 if (!_pd_uploader_flush_stubbed) { 106 Method m = class_getInstanceMethod(uploader, 107 @selector(flushThenUploadWithCompletionHandler:isOnForeground:)); 108 if (m) { 109 method_setImplementation(m, (IMP)_pd_mlkit_noop_flush_upload); 110 _pd_uploader_flush_stubbed = YES; 111 } 112 } 113 } 114 if (!_pd_metalogger_stubbed) { 115 Class meta = NSClassFromString(@"MLKITx_CCTClearcutMetaLogger"); 116 if (meta) { 117 Method m = class_getInstanceMethod(meta, 118 @selector(logCounters:)); 119 if (m) { 120 method_setImplementation(m, (IMP)_pd_mlkit_noop_log_counters); 121 _pd_metalogger_stubbed = YES; 122 } 123 } 124 } 125} 126 127__attribute__((visibility("default"))) 128void mlkit_set_resource_dir(const char *path) { 129 if (path) { 130 _mlkitResourceDir = [NSString stringWithUTF8String:path]; 131 } else { 132 _mlkitResourceDir = nil; 133 } 134 _pd_install_clearcut_stub(); 135} 136 137static NSSet<NSString *> *mlkitBundleNames(void) { 138 static NSSet *names; 139 static dispatch_once_t once; 140 dispatch_once(&once, ^{ 141 names = [NSSet setWithObjects: 142 @"MLKitPoseDetectionAccurateResources", 143 @"MLKitPoseDetectionCommonResources", 144 @"MLKitXenoResources", 145 nil]; 146 }); 147 return names; 148} 149 150// No-op replacement for -[MLKITx_CCTClearcutLogger log:completion:]. 151// MLKit's Clearcut telemetry subsystem crashes at runtime on some iOS 152// versions when it tries to compute a log-context URL (the selector 153// `+[MLKITx_CCTClearcutFileUtility computeUrlForLogContextDir:context:bundleId:]` 154// goes missing at the app-link step even with -ObjC when the consumer 155// builds a static framework). Telemetry isn't needed for pose detection, 156// so we replace the top-level log entry with a completion-handler call 157// that signals success but does nothing. 158static void _pd_mlkit_noop_log(id self, SEL _cmd, id event, id completion) { 159 if (completion) { 160 void (^block)(BOOL, NSError *) = completion; 161 block(YES, nil); 162 } 163} 164 165// No-op replacement for the 7-arg LogWriter writeLog. Some callers bypass 166// CCTClearcutLogger and call CCTLogWriter directly stubbing only the 167// Logger isn't enough. This signature matches the actual method: 168// -[MLKITx_CCTLogWriter writeLog:pseudonymousID:logDirectory:clock: 169// logTransformers:completionQueue:completion:] 170static void _pd_mlkit_noop_writelog(id self, SEL _cmd, 171 id log, id pid, id logDir, id clock, 172 id transformers, id completionQueue, 173 id completion) { 174 if (completion) { 175 void (^block)(BOOL, NSError *) = completion; 176 if (completionQueue) { 177 // Preserve semantics: callers typically want the completion 178 // delivered on their queue. dispatch_async into it, else call 179 // inline. 180 dispatch_async((dispatch_queue_t)completionQueue, ^{ 181 block(YES, nil); 182 }); 183 } else { 184 block(YES, nil); 185 } 186 } 187} 188 189// Fallback for the missing +[MLKITx_CCTClearcutFileUtility 190// computeUrlForLogContextDir:context:bundleId:] class method. Return the 191// logContextDir NSURL unchanged good enough to satisfy any caller that 192// slips past our writeLog stub. 193static id _pd_mlkit_compute_url(Class self, SEL _cmd, 194 id logContextDir, id context, id bundleId) { 195 return logContextDir; 196} 197 198// MLKit also starts a periodic auto-upload timer that crashes when it 199// reaches CCTClearcutMetaLogger logCounters:. Separate code path from 200// writeLog. We stub it at multiple levels for belt-and-braces: 201// top: -[MLKITx_CCTClearcutUploader startAutoUpload] 202// cleanest, prevents the timer from starting 203// middle: -[MLKITx_CCTClearcutUploader 204// flushThenUploadWithCompletionHandler:isOnForeground:] 205// invoked directly when foreground-entering; no-op + call 206// completion 207// bottom: -[MLKITx_CCTClearcutMetaLogger logCounters:] 208// crash site itself; fallback if upper stubs miss it 209static void _pd_mlkit_noop_start_auto_upload(id self, SEL _cmd) { 210 // Do nothing auto-upload stays off. 211} 212 213static void _pd_mlkit_noop_flush_upload(id self, SEL _cmd, 214 id completion, BOOL isForeground) { 215 if (completion) { 216 void (^block)(BOOL) = completion; 217 block(YES); 218 } 219} 220 221static void _pd_mlkit_noop_log_counters(id self, SEL _cmd, id counters) { 222 // Do nothing counters are silently dropped. 223} 224 225@interface NSBundle (PoseDetectionMLKitRedirect) 226@end 227 228@implementation NSBundle (PoseDetectionMLKitRedirect) 229 230+ (void)load { 231 static dispatch_once_t once; 232 dispatch_once(&once, ^{ 233 Method origM = class_getInstanceMethod(self, 234 @selector(URLForResource:withExtension:)); 235 Method newM = class_getInstanceMethod(self, 236 @selector(pd_mlkit_URLForResource:withExtension:)); 237 method_exchangeImplementations(origM, newM); 238 239 // Try to install the Clearcut no-op now; if the class isn't 240 // registered yet (MLKit loaded after us), this is a silent no-op 241 // and mlkit_set_resource_dir re-attempts later. 242 _pd_install_clearcut_stub(); 243 }); 244} 245 246- (NSURL *)pd_mlkit_URLForResource:(NSString *)name 247 withExtension:(NSString *)ext { 248 // Swizzled this now calls the ORIGINAL implementation. 249 NSURL *fromOriginal = [self pd_mlkit_URLForResource:name withExtension:ext]; 250 if (fromOriginal) return fromOriginal; 251 252 if (!_mlkitResourceDir || !name || !ext) return nil; 253 if (![ext isEqualToString:@"bundle"]) return nil; 254 if (![mlkitBundleNames() containsObject:name]) return nil; 255 256 NSString *candidate = [_mlkitResourceDir stringByAppendingPathComponent: 257 [NSString stringWithFormat:@"%@.%@", name, ext]]; 258 BOOL isDir = NO; 259 if ([[NSFileManager defaultManager] fileExistsAtPath:candidate isDirectory:&isDir] && isDir) { 260 return [NSURL fileURLWithPath:candidate isDirectory:YES]; 261 } 262 return nil; 263} 264 265@end