// gen1recomp iOS native bridge bootstrap. // // Runs the Files-app inbox sweep (GRPickerBridge.sweepInbox) every time the // app becomes active, so ROMs/mods/saves dropped into the app's Documents // folder land in the LÖVE save directory before the Lua importer rescans. // Registered from a constructor so no LÖVE/SDL source needs to know about it. #import #import #import static NSString *GRPendingLaunchURI; static IMP GRApplicationOpenURLOriginal; static IMP GRApplicationOpenURLLegacyOriginal; static IMP GRApplicationConfigurationOriginal; static IMP GRApplicationWillFinishLaunchingOriginal; static IMP GRApplicationDidFinishLaunchingOriginal; static IMP GRApplicationSetDelegateOriginal; static Class GRApplicationDelegateClass; static IMP GRSceneWillConnectOriginal; static IMP GRSceneOpenURLContextsOriginal; static IMP GRSceneSetDelegateOriginal; static Class GRSceneDelegateClass; static void GRInstallApplicationURLHooks(void); static void GRInstallSceneURLHooksForClass(Class sceneDelegateClass); static BOOL GRIsLaunchURL(NSURL *url) { return url.scheme.length > 0 && url.host.length > 0 && [url.scheme caseInsensitiveCompare:@"gen1recomp++"] == NSOrderedSame && [url.host caseInsensitiveCompare:@"launch"] == NSOrderedSame; } static void GRStoreLaunchURL(NSURL *url) { if (!GRIsLaunchURL(url)) return; @synchronized ([UIApplication class]) { GRPendingLaunchURI = [url.absoluteString copy]; } } static NSString *GRTakeLaunchURI(void) { @synchronized ([UIApplication class]) { NSString *uri = [GRPendingLaunchURI copy]; GRPendingLaunchURI = nil; return uri; } } static void GRStoreLaunchURLContexts(NSSet *contexts) { for (id context in contexts) { if (![context respondsToSelector:@selector(URL)]) continue; GRStoreLaunchURL([context URL]); } } static void GRStoreLaunchOptions(NSDictionary *options) { if (![options isKindOfClass:[NSDictionary class]]) return; GRStoreLaunchURL(options[UIApplicationLaunchOptionsURLKey]); NSDictionary *activities = options[UIApplicationLaunchOptionsUserActivityDictionaryKey]; if (![activities isKindOfClass:[NSDictionary class]]) return; for (id activity in activities.allValues) { if (![activity respondsToSelector:@selector(webpageURL)]) continue; GRStoreLaunchURL([activity webpageURL]); } } static BOOL GRApplicationOpenURL(id self, SEL selector, UIApplication *application, NSURL *url, NSDictionary *options) { GRStoreLaunchURL(url); if (GRApplicationOpenURLOriginal) { typedef BOOL (*GROpenURL)(id, SEL, UIApplication *, NSURL *, NSDictionary *); return ((GROpenURL)GRApplicationOpenURLOriginal)(self, selector, application, url, options); } return YES; } static BOOL GRApplicationOpenURLLegacy(id self, SEL selector, UIApplication *application, NSURL *url, NSString *sourceApplication, id annotation) { GRStoreLaunchURL(url); if (GRApplicationOpenURLLegacyOriginal) { typedef BOOL (*GROpenURLLegacy)(id, SEL, UIApplication *, NSURL *, NSString *, id); return ((GROpenURLLegacy)GRApplicationOpenURLLegacyOriginal)( self, selector, application, url, sourceApplication, annotation); } return YES; } static BOOL GRApplicationWillFinishLaunching(id self, SEL selector, UIApplication *application, NSDictionary *options) { GRStoreLaunchOptions(options); if (GRApplicationWillFinishLaunchingOriginal) { typedef BOOL (*GRWillFinishLaunching)(id, SEL, UIApplication *, NSDictionary *); return ((GRWillFinishLaunching)GRApplicationWillFinishLaunchingOriginal)( self, selector, application, options); } return YES; } static BOOL GRApplicationDidFinishLaunching(id self, SEL selector, UIApplication *application, NSDictionary *options) { GRStoreLaunchOptions(options); if (GRApplicationDidFinishLaunchingOriginal) { typedef BOOL (*GRDidFinishLaunching)(id, SEL, UIApplication *, NSDictionary *); return ((GRDidFinishLaunching)GRApplicationDidFinishLaunchingOriginal)( self, selector, application, options); } return YES; } static id GRApplicationConfiguration(id self, SEL selector, UIApplication *application, id session, id options) { if ([options respondsToSelector:@selector(URLContexts)]) { GRStoreLaunchURLContexts([options URLContexts]); } if (GRApplicationConfigurationOriginal) { typedef id (*GRConfiguration)(id, SEL, UIApplication *, id, id); return ((GRConfiguration)GRApplicationConfigurationOriginal)( self, selector, application, session, options); } return nil; } static void GRSceneWillConnect(id self, SEL selector, id scene, id session, id options) { if ([options respondsToSelector:@selector(URLContexts)]) { GRStoreLaunchURLContexts([options URLContexts]); } if (GRSceneWillConnectOriginal) { typedef void (*GRWillConnect)(id, SEL, id, id, id); ((GRWillConnect)GRSceneWillConnectOriginal)( self, selector, scene, session, options); } } static void GRSceneOpenURLContexts(id self, SEL selector, id scene, NSSet *contexts) { GRStoreLaunchURLContexts(contexts); if (GRSceneOpenURLContextsOriginal) { typedef void (*GROpenURLContexts)(id, SEL, id, NSSet *); ((GROpenURLContexts)GRSceneOpenURLContextsOriginal)( self, selector, scene, contexts); } } static void GRApplicationSetDelegate(id self, SEL selector, id delegate) { typedef void (*GRSetDelegate)(id, SEL, id); ((GRSetDelegate)GRApplicationSetDelegateOriginal)(self, selector, delegate); GRInstallApplicationURLHooks(); if (delegate) GRInstallSceneURLHooksForClass([delegate class]); } static void GRSceneSetDelegate(id self, SEL selector, id delegate) { typedef void (*GRSetDelegate)(id, SEL, id); ((GRSetDelegate)GRSceneSetDelegateOriginal)(self, selector, delegate); if (delegate) GRInstallSceneURLHooksForClass([delegate class]); } static void GRInstallDelegateURLHooks(void) { Method applicationSetDelegate = class_getInstanceMethod( [UIApplication class], @selector(setDelegate:)); if (applicationSetDelegate && !GRApplicationSetDelegateOriginal) { GRApplicationSetDelegateOriginal = method_getImplementation(applicationSetDelegate); method_setImplementation(applicationSetDelegate, (IMP)GRApplicationSetDelegate); } Method sceneSetDelegate = class_getInstanceMethod([UIScene class], @selector(setDelegate:)); if (sceneSetDelegate && !GRSceneSetDelegateOriginal) { GRSceneSetDelegateOriginal = method_getImplementation(sceneSetDelegate); method_setImplementation(sceneSetDelegate, (IMP)GRSceneSetDelegate); } } static void GRInstallSceneURLHooksForClass(Class sceneDelegateClass) { if (!sceneDelegateClass || sceneDelegateClass == GRSceneDelegateClass) return; SEL willConnect = @selector(scene:willConnectToSession:options:); SEL openURLContexts = @selector(scene:openURLContexts:); Method willConnectMethod = class_getInstanceMethod(sceneDelegateClass, willConnect); Method openURLContextsMethod = class_getInstanceMethod(sceneDelegateClass, openURLContexts); GRSceneDelegateClass = sceneDelegateClass; GRSceneWillConnectOriginal = willConnectMethod ? method_getImplementation(willConnectMethod) : NULL; GRSceneOpenURLContextsOriginal = openURLContextsMethod ? method_getImplementation(openURLContextsMethod) : NULL; if (willConnectMethod) { if (!class_addMethod(sceneDelegateClass, willConnect, (IMP)GRSceneWillConnect, method_getTypeEncoding(willConnectMethod))) { method_setImplementation(willConnectMethod, (IMP)GRSceneWillConnect); } } else { class_addMethod(sceneDelegateClass, willConnect, (IMP)GRSceneWillConnect, "v@:@@@"); } if (openURLContextsMethod) { if (!class_addMethod(sceneDelegateClass, openURLContexts, (IMP)GRSceneOpenURLContexts, method_getTypeEncoding(openURLContextsMethod))) { method_setImplementation(openURLContextsMethod, (IMP)GRSceneOpenURLContexts); } } else { class_addMethod(sceneDelegateClass, openURLContexts, (IMP)GRSceneOpenURLContexts, "v@:@@"); } } static void GRInstallSceneURLHooks(void) { for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { id delegate = scene.delegate; if (delegate) GRInstallSceneURLHooksForClass([delegate class]); } } static void GRInstallApplicationURLHooks(void) { id delegate = UIApplication.sharedApplication.delegate; Class delegateClass = delegate ? [delegate class] : Nil; if (!delegateClass || delegateClass == GRApplicationDelegateClass) return; GRApplicationDelegateClass = delegateClass; GRApplicationOpenURLOriginal = NULL; GRApplicationOpenURLLegacyOriginal = NULL; GRApplicationConfigurationOriginal = NULL; GRApplicationWillFinishLaunchingOriginal = NULL; GRApplicationDidFinishLaunchingOriginal = NULL; SEL willFinish = @selector(application:willFinishLaunchingWithOptions:); Method willFinishMethod = class_getInstanceMethod(delegateClass, willFinish); if (willFinishMethod) { GRApplicationWillFinishLaunchingOriginal = method_getImplementation(willFinishMethod); if (!class_addMethod(delegateClass, willFinish, (IMP)GRApplicationWillFinishLaunching, method_getTypeEncoding(willFinishMethod))) { method_setImplementation(willFinishMethod, (IMP)GRApplicationWillFinishLaunching); } } else { class_addMethod(delegateClass, willFinish, (IMP)GRApplicationWillFinishLaunching, "c@:@@"); } SEL didFinish = @selector(application:didFinishLaunchingWithOptions:); Method didFinishMethod = class_getInstanceMethod(delegateClass, didFinish); if (didFinishMethod) { GRApplicationDidFinishLaunchingOriginal = method_getImplementation(didFinishMethod); if (!class_addMethod(delegateClass, didFinish, (IMP)GRApplicationDidFinishLaunching, method_getTypeEncoding(didFinishMethod))) { method_setImplementation(didFinishMethod, (IMP)GRApplicationDidFinishLaunching); } } else { class_addMethod(delegateClass, didFinish, (IMP)GRApplicationDidFinishLaunching, "c@:@@"); } SEL openURL = @selector(application:openURL:options:); Method openURLMethod = class_getInstanceMethod(delegateClass, openURL); if (openURLMethod) { GRApplicationOpenURLOriginal = method_getImplementation(openURLMethod); if (!class_addMethod(delegateClass, openURL, (IMP)GRApplicationOpenURL, method_getTypeEncoding(openURLMethod))) { method_setImplementation(openURLMethod, (IMP)GRApplicationOpenURL); } } else { class_addMethod(delegateClass, openURL, (IMP)GRApplicationOpenURL, "c@:@@@"); } SEL configuration = @selector(application:configurationForConnectingSceneSession:options:); Method configurationMethod = class_getInstanceMethod(delegateClass, configuration); if (configurationMethod) { GRApplicationConfigurationOriginal = method_getImplementation(configurationMethod); if (!class_addMethod(delegateClass, configuration, (IMP)GRApplicationConfiguration, method_getTypeEncoding(configurationMethod))) { method_setImplementation(configurationMethod, (IMP)GRApplicationConfiguration); } } SEL legacyOpenURL = @selector(application:openURL:sourceApplication:annotation:); Method legacyMethod = class_getInstanceMethod(delegateClass, legacyOpenURL); if (legacyMethod) { GRApplicationOpenURLLegacyOriginal = method_getImplementation(legacyMethod); if (!class_addMethod(delegateClass, legacyOpenURL, (IMP)GRApplicationOpenURLLegacy, method_getTypeEncoding(legacyMethod))) { method_setImplementation(legacyMethod, (IMP)GRApplicationOpenURLLegacy); } } } @interface NSURL (GRWebClipDataURL) - (BOOL)safari_isHTTPFamilyURL; @end @implementation NSURL (GRWebClipDataURL) - (BOOL)safari_isHTTPFamilyURL { return YES; } @end @interface GRDeviceBridge : NSObject + (NSString *)deviceModel; + (NSString *)launchURI; @end @implementation GRDeviceBridge + (NSString *)deviceModel { #if TARGET_OS_SIMULATOR NSString *simulatorModel = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"]; if (simulatorModel.length > 0) return simulatorModel; #endif struct utsname systemInfo; if (uname(&systemInfo) == 0) { NSString *model = [NSString stringWithUTF8String:systemInfo.machine]; if (model.length > 0) return model; } return @""; } + (NSString *)launchURI { return GRTakeLaunchURI() ?: @""; } @end __attribute__((constructor)) static void GRBootstrapInstall(void) { GRInstallDelegateURLHooks(); NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { GRStoreLaunchOptions(note.userInfo); GRInstallApplicationURLHooks(); GRInstallSceneURLHooks(); }]; [center addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { GRInstallApplicationURLHooks(); GRInstallSceneURLHooks(); }]; [center addObserverForName:UISceneWillConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { GRInstallSceneURLHooks(); id scene = note.object; id delegate = [scene respondsToSelector:@selector(delegate)] ? [scene delegate] : nil; if (delegate) GRInstallSceneURLHooksForClass([delegate class]); }]; [center addObserverForName:UISceneDidActivateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { GRInstallSceneURLHooks(); }]; dispatch_async(dispatch_get_main_queue(), ^{ GRInstallApplicationURLHooks(); GRInstallSceneURLHooks(); }); [center addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { Class bridge = NSClassFromString(@"GRPickerBridge"); if ([bridge respondsToSelector:@selector(preparePublicDocuments)]) { [bridge performSelector:@selector(preparePublicDocuments)]; } if ([bridge respondsToSelector:@selector(sweepInbox)]) { [bridge performSelector:@selector(sweepInbox)]; } }]; }