Files
2026-08-30 18:45:42 +02:00

405 lines
16 KiB
Objective-C

// 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 <UIKit/UIKit.h>
#import <objc/runtime.h>
#import <sys/utsname.h>
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)];
}
}];
}