2021-08-23 12:02:12 +00:00
|
|
|
/*
|
|
|
|
Copyright 2021 flyinghead
|
|
|
|
Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
|
|
|
|
|
|
|
|
This file is part of Flycast.
|
|
|
|
|
|
|
|
Flycast is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Flycast is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2021-08-06 08:30:30 +00:00
|
|
|
#import "FlycastViewController.h"
|
2021-08-12 09:22:22 +00:00
|
|
|
#import <Network/Network.h>
|
2021-08-23 12:02:12 +00:00
|
|
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
#import <OpenGLES/ES3/gl.h>
|
|
|
|
#import <OpenGLES/ES3/glext.h>
|
|
|
|
#import <OpenGLES/EAGL.h>
|
|
|
|
|
2021-08-12 09:22:22 +00:00
|
|
|
#import "PadViewController.h"
|
|
|
|
#import "EmulatorView.h"
|
|
|
|
|
2021-08-06 08:30:30 +00:00
|
|
|
#include "types.h"
|
|
|
|
#include "wsi/context.h"
|
|
|
|
#include "rend/mainui.h"
|
|
|
|
#include "emulator.h"
|
|
|
|
#include "log/LogManager.h"
|
|
|
|
#include "stdclass.h"
|
|
|
|
#include "cfg/option.h"
|
2021-08-23 12:02:12 +00:00
|
|
|
#include "ios_gamepad.h"
|
2021-09-11 11:08:35 +00:00
|
|
|
#include "ios_keyboard.h"
|
2021-09-14 09:45:27 +00:00
|
|
|
#include "ios_mouse.h"
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-08-10 15:04:36 +00:00
|
|
|
//@import AltKit;
|
|
|
|
#import "AltKit/AltKit-Swift.h"
|
2021-08-10 09:32:22 +00:00
|
|
|
|
2021-08-10 15:04:36 +00:00
|
|
|
std::string iosJitStatus;
|
2021-08-12 09:22:22 +00:00
|
|
|
static bool iosJitAuthorized;
|
|
|
|
static __unsafe_unretained FlycastViewController *flycastViewController;
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
std::map<GCController *, std::shared_ptr<IOSGamepad>> IOSGamepad::controllers;
|
2021-09-11 11:08:35 +00:00
|
|
|
std::map<GCKeyboard *, std::shared_ptr<IOSKeyboard>> IOSKeyboard::keyboards;
|
2021-09-14 09:45:27 +00:00
|
|
|
std::map<GCMouse *, std::shared_ptr<IOSMouse>> IOSMouse::mice;
|
2021-08-23 12:02:12 +00:00
|
|
|
|
2021-08-06 08:30:30 +00:00
|
|
|
void common_linux_setup();
|
|
|
|
|
2021-09-14 09:45:27 +00:00
|
|
|
static bool lockedPointer;
|
|
|
|
static void updatePointerLock(Event event)
|
|
|
|
{
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
|
|
bool hasChanged = NO;
|
|
|
|
switch (event) {
|
|
|
|
case Event::Resume:
|
|
|
|
lockedPointer = YES;
|
|
|
|
hasChanged = YES;
|
|
|
|
break;
|
|
|
|
case Event::Pause:
|
|
|
|
case Event::Terminate:
|
|
|
|
lockedPointer = NO;
|
|
|
|
hasChanged = YES;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hasChanged) {
|
|
|
|
[flycastViewController setNeedsUpdateOfPrefersPointerLocked];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-12 09:22:22 +00:00
|
|
|
@interface FlycastViewController () <UIDocumentPickerDelegate>
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
@property (strong, nonatomic) EAGLContext *context;
|
2021-08-12 09:22:22 +00:00
|
|
|
@property (strong, nonatomic) PadViewController *padController;
|
|
|
|
|
|
|
|
@property (nonatomic) iCadeReaderView* iCadeReader;
|
2021-09-11 11:08:35 +00:00
|
|
|
@property (nonatomic, strong) id gamePadConnectObserver;
|
|
|
|
@property (nonatomic, strong) id gamePadDisconnectObserver;
|
|
|
|
@property (nonatomic, strong) id keyboardConnectObserver;
|
|
|
|
@property (nonatomic, strong) id keyboardDisconnectObserver;
|
2021-09-14 09:45:27 +00:00
|
|
|
@property (nonatomic, strong) id mouseConnectObserver;
|
|
|
|
@property (nonatomic, strong) id mouseDisconnectObserver;
|
2021-08-12 09:22:22 +00:00
|
|
|
|
|
|
|
@property (nonatomic, strong) nw_path_monitor_t monitor;
|
|
|
|
@property (nonatomic, strong) dispatch_queue_t monitorQueue;
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
extern int screen_dpi;
|
|
|
|
|
2021-08-12 09:22:22 +00:00
|
|
|
@implementation FlycastViewController
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-08-12 09:22:22 +00:00
|
|
|
- (id)initWithCoder:(NSCoder *)coder
|
2021-08-06 08:30:30 +00:00
|
|
|
{
|
2021-08-12 09:22:22 +00:00
|
|
|
self = [super initWithCoder:coder];
|
|
|
|
if (self)
|
|
|
|
flycastViewController = self;
|
|
|
|
return self;
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidLoad
|
|
|
|
{
|
|
|
|
[super viewDidLoad];
|
|
|
|
|
|
|
|
LogManager::Init();
|
|
|
|
|
|
|
|
std::string homedir = [[[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] objectAtIndex:0] path] UTF8String];
|
|
|
|
homedir += "/";
|
|
|
|
set_user_config_dir(homedir);
|
|
|
|
set_user_data_dir(homedir);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
int ret = task_set_exception_ports(
|
|
|
|
mach_task_self(),
|
|
|
|
EXC_MASK_BAD_ACCESS,
|
|
|
|
MACH_PORT_NULL,
|
|
|
|
EXCEPTION_DEFAULT,
|
|
|
|
0);
|
|
|
|
|
|
|
|
if (ret != KERN_SUCCESS) {
|
|
|
|
printf("task_set_exception_ports: %s\n", mach_error_string(ret));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
common_linux_setup();
|
|
|
|
|
|
|
|
flycast_init(0, nullptr);
|
|
|
|
config::ContentPath.get().clear();
|
|
|
|
config::ContentPath.get().push_back(homedir);
|
|
|
|
|
|
|
|
#if !TARGET_OS_TV
|
|
|
|
self.padController = [[PadViewController alloc] initWithNibName:@"PadViewController" bundle:nil];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
2021-08-10 09:32:22 +00:00
|
|
|
if (!self.context) {
|
|
|
|
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
|
|
|
if (!self.context) {
|
|
|
|
NSLog(@"Failed to create ES context");
|
|
|
|
}
|
|
|
|
}
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-08-10 09:32:22 +00:00
|
|
|
EmulatorView *emuView = (EmulatorView *)self.view;
|
|
|
|
emuView.context = self.context;
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
// Set preferred refresh rate
|
|
|
|
[self setPreferredFramesPerSecond:60];
|
2021-08-10 09:32:22 +00:00
|
|
|
[EAGLContext setCurrentContext:self.context];
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-09-11 11:08:35 +00:00
|
|
|
if (@available(iOS 14.0, *)) {
|
|
|
|
self.keyboardConnectObserver = [[NSNotificationCenter defaultCenter]
|
|
|
|
addObserverForName:GCKeyboardDidConnectNotification object:nil queue:[NSOperationQueue mainQueue]
|
|
|
|
usingBlock:^(NSNotification *note) {
|
|
|
|
GCKeyboard *keyboard = note.object;
|
|
|
|
IOSKeyboard::addKeyboard(keyboard);
|
|
|
|
}];
|
|
|
|
|
|
|
|
self.keyboardDisconnectObserver = [[NSNotificationCenter defaultCenter]
|
|
|
|
addObserverForName:GCKeyboardDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue]
|
|
|
|
usingBlock:^(NSNotification *note) {
|
|
|
|
GCKeyboard *keyboard = note.object;
|
|
|
|
IOSKeyboard::removeKeyboard(keyboard);
|
|
|
|
}];
|
2021-09-14 09:45:27 +00:00
|
|
|
|
|
|
|
self.mouseConnectObserver = [[NSNotificationCenter defaultCenter]
|
|
|
|
addObserverForName:GCMouseDidConnectNotification object:nil queue:[NSOperationQueue mainQueue]
|
|
|
|
usingBlock:^(NSNotification *note) {
|
|
|
|
GCMouse *mouse = note.object;
|
|
|
|
IOSMouse::addMouse(mouse);
|
|
|
|
}];
|
|
|
|
|
|
|
|
self.mouseDisconnectObserver = [[NSNotificationCenter defaultCenter]
|
|
|
|
addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue]
|
|
|
|
usingBlock:^(NSNotification *note) {
|
|
|
|
GCMouse *mouse = note.object;
|
|
|
|
IOSMouse::removeMouse(mouse);
|
|
|
|
}];
|
|
|
|
|
|
|
|
EventManager::listen(Event::Resume, updatePointerLock);
|
|
|
|
EventManager::listen(Event::Pause, updatePointerLock);
|
|
|
|
EventManager::listen(Event::Terminate, updatePointerLock);
|
2021-09-11 11:08:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.gamePadConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
2021-08-23 12:02:12 +00:00
|
|
|
GCController *controller = note.object;
|
|
|
|
IOSGamepad::addController(controller);
|
|
|
|
#if !TARGET_OS_TV
|
|
|
|
if (IOSGamepad::controllerConnected())
|
|
|
|
[self.padController hideController];
|
|
|
|
#endif
|
2021-08-06 08:30:30 +00:00
|
|
|
}];
|
2021-09-11 11:08:35 +00:00
|
|
|
self.gamePadDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
2021-08-23 12:02:12 +00:00
|
|
|
GCController *controller = note.object;
|
|
|
|
IOSGamepad::removeController(controller);
|
|
|
|
#if !TARGET_OS_TV
|
|
|
|
if (!IOSGamepad::controllerConnected())
|
|
|
|
[self.padController showController:self.view];
|
|
|
|
#endif
|
2021-08-06 08:30:30 +00:00
|
|
|
}];
|
2021-08-23 12:02:12 +00:00
|
|
|
|
|
|
|
for (GCController *controller in [GCController controllers])
|
|
|
|
IOSGamepad::addController(controller);
|
2021-08-06 08:30:30 +00:00
|
|
|
|
|
|
|
#if !TARGET_OS_TV
|
|
|
|
[self addChildViewController:self.padController];
|
|
|
|
self.padController.view.frame = self.view.bounds;
|
|
|
|
self.padController.view.translatesAutoresizingMaskIntoConstraints = YES;
|
2021-08-23 12:02:12 +00:00
|
|
|
self.padController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
|
|
if (IOSGamepad::controllerConnected())
|
|
|
|
[self.padController hideController];
|
2021-08-06 08:30:30 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
self.iCadeReader = [[iCadeReaderView alloc] init];
|
|
|
|
[self.view addSubview:self.iCadeReader];
|
|
|
|
self.iCadeReader.delegate = self;
|
|
|
|
self.iCadeReader.active = YES;
|
2021-08-10 09:32:22 +00:00
|
|
|
// TODO iCade handlers
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-09-27 18:29:23 +00:00
|
|
|
settings.display.width = roundf([[UIScreen mainScreen] nativeBounds].size.width);
|
|
|
|
settings.display.height = roundf([[UIScreen mainScreen] nativeBounds].size.height);
|
|
|
|
if (settings.display.width < settings.display.height)
|
|
|
|
std::swap(settings.display.width, settings.display.height);
|
2021-08-06 08:30:30 +00:00
|
|
|
float scale = 1;
|
|
|
|
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
|
|
|
|
scale = [[UIScreen mainScreen] scale];
|
|
|
|
}
|
|
|
|
screen_dpi = roundf(160 * scale);
|
|
|
|
InitRenderApi();
|
|
|
|
mainui_init();
|
|
|
|
mainui_enabled = true;
|
2021-08-23 12:02:12 +00:00
|
|
|
|
2021-08-10 15:04:36 +00:00
|
|
|
[self altKitStart];
|
2021-08-06 08:30:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
- (BOOL)prefersStatusBarHidden
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2021-09-14 09:45:27 +00:00
|
|
|
- (BOOL)prefersPointerLocked
|
|
|
|
{
|
|
|
|
return lockedPointer;
|
|
|
|
}
|
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
- (UIStatusBarStyle)preferredStatusBarStyle
|
|
|
|
{
|
|
|
|
return UIStatusBarStyleLightContent;
|
|
|
|
}
|
|
|
|
|
2021-08-12 09:22:22 +00:00
|
|
|
-(UIRectEdge)preferredScreenEdgesDeferringSystemGestures
|
|
|
|
{
|
|
|
|
return UIRectEdgeAll;
|
|
|
|
}
|
|
|
|
|
2021-08-06 08:30:30 +00:00
|
|
|
- (void)dealloc
|
|
|
|
{
|
|
|
|
if ([EAGLContext currentContext] == self.context) {
|
|
|
|
[EAGLContext setCurrentContext:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-10 09:32:22 +00:00
|
|
|
- (void)altKitStart
|
2021-08-06 08:30:30 +00:00
|
|
|
{
|
2021-08-10 09:32:22 +00:00
|
|
|
NSLog(@"Starting AltKit discovery");
|
|
|
|
|
|
|
|
[[ALTServerManager sharedManager] autoconnectWithCompletionHandler:^(ALTServerConnection *connection, NSError *error) {
|
|
|
|
if (error)
|
|
|
|
{
|
2021-08-12 09:22:22 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
|
|
iosJitStatus = "Failed: " + std::string([error.description UTF8String]);
|
|
|
|
});
|
2021-08-10 09:32:22 +00:00
|
|
|
return NSLog(@"Could not auto-connect to server. %@", error);
|
|
|
|
}
|
|
|
|
|
|
|
|
[connection enableUnsignedCodeExecutionWithCompletionHandler:^(BOOL success, NSError *error) {
|
2021-08-12 09:22:22 +00:00
|
|
|
iosJitAuthorized = success;
|
2021-08-10 09:32:22 +00:00
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
NSLog(@"Successfully enabled JIT compilation!");
|
2021-08-12 09:22:22 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
|
|
iosJitStatus = "OK";
|
|
|
|
nw_path_monitor_cancel(self.monitor);
|
|
|
|
});
|
2021-08-10 09:32:22 +00:00
|
|
|
[[ALTServerManager sharedManager] stopDiscovering];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-12 09:22:22 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
|
|
iosJitStatus = "Failed: " + std::string([error.description UTF8String]);
|
|
|
|
});
|
2021-08-10 09:32:22 +00:00
|
|
|
NSLog(@"Could not enable JIT compilation. %@", error);
|
|
|
|
}
|
|
|
|
|
|
|
|
[connection disconnect];
|
|
|
|
}];
|
|
|
|
}];
|
2021-08-12 09:22:22 +00:00
|
|
|
|
|
|
|
dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT);
|
|
|
|
self.monitorQueue = dispatch_queue_create("com.flycast.network-monitor", attrs);
|
|
|
|
self.monitor = nw_path_monitor_create();
|
|
|
|
nw_path_monitor_set_queue(self.monitor, self.monitorQueue);
|
|
|
|
nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t _Nonnull path) {
|
|
|
|
nw_path_status_t status = nw_path_get_status(path);
|
|
|
|
if (!iosJitAuthorized && (status == nw_path_status_satisfied || status == nw_path_status_satisfiable)) {
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
|
|
|
[[ALTServerManager sharedManager] stopDiscovering];
|
|
|
|
iosJitStatus = "Connecting...";
|
|
|
|
[[ALTServerManager sharedManager] startDiscovering];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
nw_path_monitor_start(self.monitor);
|
2021-08-06 08:30:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
- (void)viewSafeAreaInsetsDidChange
|
2021-08-06 08:30:30 +00:00
|
|
|
{
|
2021-08-23 12:02:12 +00:00
|
|
|
[super viewSafeAreaInsetsDidChange];
|
|
|
|
float scale = self.view.contentScaleFactor;
|
|
|
|
gui_set_insets(self.view.safeAreaInsets.left * scale, self.view.safeAreaInsets.right * scale,
|
|
|
|
self.view.safeAreaInsets.top * scale, self.view.safeAreaInsets.bottom * scale);
|
2021-08-06 08:30:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
#pragma mark - GLKView and GLKViewController delegate methods
|
2021-08-06 08:30:30 +00:00
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
- (void)update
|
|
|
|
{
|
2021-08-16 09:46:45 +00:00
|
|
|
|
2021-08-06 08:30:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
|
|
|
|
{
|
2021-08-23 12:02:12 +00:00
|
|
|
#if !TARGET_OS_TV
|
2021-09-29 08:22:58 +00:00
|
|
|
if (emu.running() != [self.padController isControllerVisible] && !IOSGamepad::controllerConnected())
|
2021-08-06 08:30:30 +00:00
|
|
|
{
|
2021-09-29 08:22:58 +00:00
|
|
|
if (emu.running())
|
2021-08-06 08:30:30 +00:00
|
|
|
[self.padController showController:self.view];
|
|
|
|
else
|
|
|
|
[self.padController hideController];
|
|
|
|
}
|
2021-08-23 12:02:12 +00:00
|
|
|
#endif
|
2021-08-06 08:30:30 +00:00
|
|
|
mainui_rend_frame();
|
|
|
|
}
|
2021-08-23 12:02:12 +00:00
|
|
|
/*
|
|
|
|
- (void)pickIosFile
|
2021-08-12 09:22:22 +00:00
|
|
|
{
|
2021-08-23 12:02:12 +00:00
|
|
|
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"com.flyinghead.flycast.disk-image", @"com.pkware.zip-archive"] inMode:UIDocumentPickerModeOpen];
|
|
|
|
picker.delegate = self;
|
|
|
|
picker.allowsMultipleSelection = true;
|
|
|
|
|
|
|
|
[self presentViewController:picker animated:YES completion:nil];
|
2021-08-12 09:22:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)pickIosFolder
|
|
|
|
{
|
|
|
|
if (@available(iOS 14.0, *)) {
|
|
|
|
UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[UTTypeFolder]];
|
|
|
|
picker.delegate = self;
|
|
|
|
|
|
|
|
[self presentViewController:picker animated:YES completion:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls
|
|
|
|
{
|
|
|
|
for (NSURL *url in urls) {
|
2021-08-23 12:02:12 +00:00
|
|
|
if (url.fileURL)
|
|
|
|
{
|
|
|
|
[url startAccessingSecurityScopedResource];
|
|
|
|
gui_add_content_path(url.fileSystemRepresentation);
|
|
|
|
}
|
2021-08-12 09:22:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2021-08-06 08:30:30 +00:00
|
|
|
@end
|
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
void pickIosFolder()
|
2021-08-06 08:30:30 +00:00
|
|
|
{
|
2021-08-23 12:02:12 +00:00
|
|
|
// [flycastViewController pickIosFolder];
|
2021-08-06 08:30:30 +00:00
|
|
|
}
|
2021-08-12 09:22:22 +00:00
|
|
|
|
2021-08-23 12:02:12 +00:00
|
|
|
void pickIosFile()
|
2021-08-12 09:22:22 +00:00
|
|
|
{
|
2021-08-23 12:02:12 +00:00
|
|
|
// [flycastViewController pickIosFile];
|
2021-08-12 09:22:22 +00:00
|
|
|
}
|