mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
287 lines
10 KiB
Plaintext
287 lines
10 KiB
Plaintext
#import "AppDelegate.h"
|
|
#import "EmulatorRunner.h"
|
|
#import "armsx_bridge.h"
|
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
|
|
|
static NSString *const ARMSXLibraryBookmarksKey = @"ARMSXLibraryDirectoryBookmarks";
|
|
|
|
@interface AppDelegate () <UIDocumentPickerDelegate>
|
|
@property (nonatomic, strong) EmulatorRunner *emulatorRunner;
|
|
@property (nonatomic, copy) NSString *launchProtocolURL;
|
|
@property (nonatomic, assign) BOOL pendingLibraryRecursive;
|
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSURL *> *accessedLibraryURLs;
|
|
- (void)presentLibraryDirectoryPickerRecursive:(BOOL)recursive;
|
|
- (void)removePersistedLibraryDirectory:(NSString *)path;
|
|
@end
|
|
|
|
static void ARMSXRequestLibraryDirectory(int recursive, void *userdata) {
|
|
AppDelegate *delegate = (__bridge AppDelegate *)userdata;
|
|
void (^present)(void) = ^{
|
|
[delegate presentLibraryDirectoryPickerRecursive:recursive != 0];
|
|
};
|
|
if (NSThread.isMainThread) {
|
|
present();
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), present);
|
|
}
|
|
}
|
|
|
|
static void ARMSXRemoveLibraryDirectory(const char *path, void *userdata) {
|
|
if (!path) {
|
|
return;
|
|
}
|
|
AppDelegate *delegate = (__bridge AppDelegate *)userdata;
|
|
NSString *directoryPath = [NSString stringWithUTF8String:path];
|
|
void (^remove)(void) = ^{
|
|
[delegate removePersistedLibraryDirectory:directoryPath];
|
|
};
|
|
if (NSThread.isMainThread) {
|
|
remove();
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), remove);
|
|
}
|
|
}
|
|
|
|
@implementation AppDelegate
|
|
|
|
- (UIViewController *)topViewController {
|
|
UIViewController *controller = self.window.rootViewController;
|
|
while (controller.presentedViewController) {
|
|
controller = controller.presentedViewController;
|
|
}
|
|
return controller;
|
|
}
|
|
|
|
- (void)presentLibraryDirectoryPickerRecursive:(BOOL)recursive {
|
|
self.pendingLibraryRecursive = recursive;
|
|
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc]
|
|
initForOpeningContentTypes:@[UTTypeFolder]
|
|
asCopy:NO];
|
|
picker.delegate = self;
|
|
picker.allowsMultipleSelection = NO;
|
|
|
|
UIViewController *presenter = [self topViewController];
|
|
if (!presenter) {
|
|
NSLog(@"ARMSX could not present the directory picker because no root view controller is active.");
|
|
return;
|
|
}
|
|
[presenter presentViewController:picker animated:YES completion:nil];
|
|
}
|
|
|
|
- (void)registerLibraryURL:(NSURL *)url recursive:(BOOL)recursive persist:(BOOL)persist {
|
|
if (!url.isFileURL || url.path.length == 0) {
|
|
return;
|
|
}
|
|
|
|
NSString *path = url.URLByStandardizingPath.path;
|
|
BOOL accessed = [url startAccessingSecurityScopedResource];
|
|
if (accessed) {
|
|
self.accessedLibraryURLs[path] = url;
|
|
}
|
|
|
|
NSString *label = nil;
|
|
[url getResourceValue:&label forKey:NSURLNameKey error:nil];
|
|
if (label.length == 0) {
|
|
label = url.lastPathComponent.length ? url.lastPathComponent : @"Game Directory";
|
|
}
|
|
psxe_register_platform_library_directory(path.UTF8String, label.UTF8String, recursive ? 1 : 0);
|
|
|
|
if (!persist) {
|
|
return;
|
|
}
|
|
|
|
NSError *bookmarkError = nil;
|
|
NSData *bookmark = [url bookmarkDataWithOptions:0
|
|
includingResourceValuesForKeys:@[NSURLNameKey]
|
|
relativeToURL:nil
|
|
error:&bookmarkError];
|
|
if (!bookmark) {
|
|
NSLog(@"ARMSX could not save persistent directory access for %@: %@", path, bookmarkError);
|
|
return;
|
|
}
|
|
|
|
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
|
NSMutableArray<NSDictionary *> *records =
|
|
[[defaults arrayForKey:ARMSXLibraryBookmarksKey] mutableCopy] ?: [NSMutableArray array];
|
|
NSIndexSet *duplicates = [records indexesOfObjectsPassingTest:
|
|
^BOOL(NSDictionary *record, NSUInteger index, BOOL *stop) {
|
|
(void)index;
|
|
(void)stop;
|
|
return [record[@"path"] isEqualToString:path];
|
|
}];
|
|
[records removeObjectsAtIndexes:duplicates];
|
|
[records addObject:@{
|
|
@"bookmark": bookmark,
|
|
@"path": path,
|
|
@"label": label,
|
|
@"recursive": @(recursive),
|
|
}];
|
|
[defaults setObject:records forKey:ARMSXLibraryBookmarksKey];
|
|
}
|
|
|
|
- (void)restoreLibraryDirectories {
|
|
NSArray<NSDictionary *> *records =
|
|
[NSUserDefaults.standardUserDefaults arrayForKey:ARMSXLibraryBookmarksKey] ?: @[];
|
|
NSMutableArray<NSDictionary *> *validRecords = [NSMutableArray array];
|
|
|
|
for (NSDictionary *record in records) {
|
|
NSData *bookmark = record[@"bookmark"];
|
|
if (![bookmark isKindOfClass:NSData.class]) {
|
|
continue;
|
|
}
|
|
|
|
BOOL stale = NO;
|
|
NSError *error = nil;
|
|
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
|
|
options:0
|
|
relativeToURL:nil
|
|
bookmarkDataIsStale:&stale
|
|
error:&error];
|
|
if (!url || error) {
|
|
NSLog(@"ARMSX could not restore a game directory bookmark: %@", error);
|
|
continue;
|
|
}
|
|
|
|
BOOL recursive = [record[@"recursive"] boolValue];
|
|
[self registerLibraryURL:url recursive:recursive persist:NO];
|
|
|
|
if (stale) {
|
|
NSError *bookmarkError = nil;
|
|
NSData *refreshed = [url bookmarkDataWithOptions:0
|
|
includingResourceValuesForKeys:@[NSURLNameKey]
|
|
relativeToURL:nil
|
|
error:&bookmarkError];
|
|
if (refreshed) {
|
|
NSMutableDictionary *updated = [record mutableCopy];
|
|
updated[@"bookmark"] = refreshed;
|
|
updated[@"path"] = url.URLByStandardizingPath.path;
|
|
[validRecords addObject:updated];
|
|
continue;
|
|
}
|
|
NSLog(@"ARMSX could not refresh a stale game directory bookmark: %@", bookmarkError);
|
|
}
|
|
[validRecords addObject:record];
|
|
}
|
|
|
|
[NSUserDefaults.standardUserDefaults setObject:validRecords forKey:ARMSXLibraryBookmarksKey];
|
|
}
|
|
|
|
- (void)removePersistedLibraryDirectory:(NSString *)path {
|
|
NSString *standardPath = [NSURL fileURLWithPath:path].URLByStandardizingPath.path;
|
|
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
|
|
NSMutableArray<NSDictionary *> *records =
|
|
[[defaults arrayForKey:ARMSXLibraryBookmarksKey] mutableCopy] ?: [NSMutableArray array];
|
|
NSIndexSet *matches = [records indexesOfObjectsPassingTest:
|
|
^BOOL(NSDictionary *record, NSUInteger index, BOOL *stop) {
|
|
(void)index;
|
|
(void)stop;
|
|
NSString *recordPath = record[@"path"];
|
|
return [recordPath isEqualToString:standardPath];
|
|
}];
|
|
[records removeObjectsAtIndexes:matches];
|
|
[defaults setObject:records forKey:ARMSXLibraryBookmarksKey];
|
|
|
|
NSURL *url = self.accessedLibraryURLs[standardPath];
|
|
if (url) {
|
|
[url stopAccessingSecurityScopedResource];
|
|
[self.accessedLibraryURLs removeObjectForKey:standardPath];
|
|
}
|
|
}
|
|
|
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller
|
|
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
|
(void)controller;
|
|
NSURL *url = urls.firstObject;
|
|
if (url) {
|
|
[self registerLibraryURL:url recursive:self.pendingLibraryRecursive persist:YES];
|
|
}
|
|
}
|
|
|
|
- (void)forceLandscapeIfNeeded {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
UIInterfaceOrientation currentOrientation = UIInterfaceOrientationUnknown;
|
|
if (@available(iOS 13.0, *)) {
|
|
currentOrientation = self.window.windowScene.interfaceOrientation;
|
|
} else {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
currentOrientation = UIApplication.sharedApplication.statusBarOrientation;
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
if (UIInterfaceOrientationIsLandscape(currentOrientation)) {
|
|
return;
|
|
}
|
|
|
|
NSNumber *value = @(UIInterfaceOrientationLandscapeRight);
|
|
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
|
|
[UIViewController attemptRotationToDeviceOrientation];
|
|
});
|
|
}
|
|
|
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
(void)application;
|
|
|
|
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
self.window.backgroundColor = [UIColor blackColor];
|
|
[self.window makeKeyAndVisible];
|
|
[self forceLandscapeIfNeeded];
|
|
self.accessedLibraryURLs = [NSMutableDictionary dictionary];
|
|
|
|
psxe_set_library_directory_picker_callback(
|
|
ARMSXRequestLibraryDirectory,
|
|
(__bridge void *)self
|
|
);
|
|
psxe_set_library_directory_removed_callback(
|
|
ARMSXRemoveLibraryDirectory,
|
|
(__bridge void *)self
|
|
);
|
|
[self restoreLibraryDirectories];
|
|
|
|
NSURL *launchURL = launchOptions[UIApplicationLaunchOptionsURLKey];
|
|
if (launchURL.absoluteString.length) {
|
|
self.launchProtocolURL = launchURL.absoluteString;
|
|
}
|
|
|
|
self.emulatorRunner = [EmulatorRunner new];
|
|
[self.emulatorRunner startWithArgs:self.launchProtocolURL.length ? @[self.launchProtocolURL] : @[]];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)applicationWillTerminate:(UIApplication *)application {
|
|
(void)application;
|
|
for (NSURL *url in self.accessedLibraryURLs.allValues) {
|
|
[url stopAccessingSecurityScopedResource];
|
|
}
|
|
[self.accessedLibraryURLs removeAllObjects];
|
|
psxe_set_library_directory_picker_callback(NULL, NULL);
|
|
psxe_set_library_directory_removed_callback(NULL, NULL);
|
|
}
|
|
|
|
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
|
|
(void)application;
|
|
(void)options;
|
|
|
|
NSString *argument = url.absoluteString;
|
|
if (argument.length == 0) {
|
|
return NO;
|
|
}
|
|
|
|
if (self.launchProtocolURL.length && [self.launchProtocolURL isEqualToString:argument]) {
|
|
self.launchProtocolURL = nil;
|
|
return YES;
|
|
}
|
|
|
|
if (!self.emulatorRunner) {
|
|
self.emulatorRunner = [EmulatorRunner new];
|
|
[self.emulatorRunner startWithArgs:@[argument]];
|
|
} else {
|
|
[self.emulatorRunner enqueueLaunchArgument:argument];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
@end
|