8
#import "ViewController.h"
9
#import "AudioEngine.h"
10
#import <GLKit/GLKit.h>
12
#include "base/display.h"
13
#include "base/timeutil.h"
14
#include "file/zip_read.h"
15
#include "input/input_state.h"
16
#include "net/resolve.h"
17
#include "ui/screen.h"
18
#include "thin3d/thin3d.h"
19
#include "input/keycodes.h"
20
#include "gfx_es2/gpu_features.h"
22
#include "Core/Config.h"
23
#include "Common/GraphicsContext.h"
24
#include "GPU/GLES/FBO.h"
26
#include <sys/types.h>
27
#include <sys/sysctl.h>
28
#include <mach/machine.h>
30
#define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
31
#define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
33
#ifndef kCFCoreFoundationVersionNumber_IOS_9_0
34
#define kCFCoreFoundationVersionNumber_IOS_9_0 1240.10
37
class IOSDummyGraphicsContext : public DummyGraphicsContext {
39
Thin3DContext *CreateThin3DContext() override {
41
return T3DCreateGLContext();
45
float dp_xscale = 1.0f;
46
float dp_yscale = 1.0f;
48
double lastSelectPress = 0.0f;
49
double lastStartPress = 0.0f;
50
bool simulateAnalog = false;
52
extern ScreenManager *screenManager;
53
InputState input_state;
55
extern std::string ram_temp_file;
56
extern bool iosCanUseJit;
57
extern bool targetIsJailbroken;
59
ViewController* sharedViewController;
60
static GraphicsContext *graphicsContext;
62
@interface ViewController ()
64
std::map<uint16_t, uint16_t> iCadeToKeyMap;
67
@property (nonatomic) EAGLContext* context;
68
@property (nonatomic) NSString* documentsPath;
69
@property (nonatomic) NSString* bundlePath;
70
@property (nonatomic) NSMutableArray* touches;
71
@property (nonatomic) AudioEngine* audioEngine;
72
@property (nonatomic) iCadeReaderView* iCadeView;
73
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
74
@property (nonatomic) GCController *gameController __attribute__((weak_import));
79
@implementation ViewController
84
sysctlbyname("hw.cputype", &type, &size, NULL, 0);
85
return type == CPU_TYPE_ARM64;
91
sharedViewController = self;
92
self.touches = [NSMutableArray array];
93
self.documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
94
self.bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"];
96
memset(&input_state, 0, sizeof(input_state));
100
ram_temp_file = [[NSTemporaryDirectory() stringByAppendingPathComponent:@"ram_tmp.file"] fileSystemRepresentation];
103
targetIsJailbroken = false;
104
NSArray *jailPath = [NSArray arrayWithObjects:
105
@"/Applications/Cydia.app",
106
@"/private/var/lib/apt",
107
@"/private/var/stash",
109
@"/usr/bin/sshd", nil];
111
for (NSString *string in jailPath) {
112
if ([[NSFileManager defaultManager] fileExistsAtPath:string]) {
113
// checking device jailbreak status in order to determine which message to show in GameSettingsScreen
114
targetIsJailbroken = true;
118
NativeInit(0, NULL, [self.documentsPath UTF8String], [self.bundlePath UTF8String], NULL);
120
iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP;
121
iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT;
122
iCadeToKeyMap[iCadeJoystickDown] = NKCODE_DPAD_DOWN;
123
iCadeToKeyMap[iCadeJoystickLeft] = NKCODE_DPAD_LEFT;
124
iCadeToKeyMap[iCadeButtonA] = NKCODE_BUTTON_9; // Select
125
iCadeToKeyMap[iCadeButtonB] = NKCODE_BUTTON_7; // LTrigger
126
iCadeToKeyMap[iCadeButtonC] = NKCODE_BUTTON_10; // Start
127
iCadeToKeyMap[iCadeButtonD] = NKCODE_BUTTON_8; // RTrigger
128
iCadeToKeyMap[iCadeButtonE] = NKCODE_BUTTON_4; // Square
129
iCadeToKeyMap[iCadeButtonF] = NKCODE_BUTTON_2; // Cross
130
iCadeToKeyMap[iCadeButtonG] = NKCODE_BUTTON_1; // Triangle
131
iCadeToKeyMap[iCadeButtonH] = NKCODE_BUTTON_3; // Circle
133
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
134
if ([GCController class]) // Checking the availability of a GameController framework
136
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
137
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
144
- (void)viewDidLoad {
147
self.view.frame = [[UIScreen mainScreen] bounds];
148
self.view.multipleTouchEnabled = YES;
149
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
153
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
156
GLKView* view = (GLKView *)self.view;
157
view.context = self.context;
158
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
159
[EAGLContext setCurrentContext:self.context];
160
self.preferredFramesPerSecond = 60;
162
float scale = [UIScreen mainScreen].scale;
164
if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) {
165
scale = [UIScreen mainScreen].nativeScale;
168
CGSize size = [[UIApplication sharedApplication].delegate window].frame.size;
170
if (size.height > size.width)
172
float h = size.height;
173
size.height = size.width;
177
g_dpi = (IS_IPAD() ? 200 : 150) * scale;
178
g_dpi_scale = 240.0f / (float)g_dpi;
179
pixel_xres = size.width * scale;
180
pixel_yres = size.height * scale;
182
dp_xres = pixel_xres * g_dpi_scale;
183
dp_yres = pixel_yres * g_dpi_scale;
185
pixel_in_dps = (float)pixel_xres / (float)dp_xres;
187
graphicsContext = new IOSDummyGraphicsContext();
189
NativeInitGraphics(graphicsContext);
191
dp_xscale = (float)dp_xres / (float)pixel_xres;
192
dp_yscale = (float)dp_yres / (float)pixel_yres;
194
self.iCadeView = [[iCadeReaderView alloc] init];
195
[self.view addSubview:self.iCadeView];
196
self.iCadeView.delegate = self;
197
self.iCadeView.active = YES;
199
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
200
if ([GCController class]) {
201
if ([[GCController controllers] count] > 0) {
202
[self setupController:[[GCController controllers] firstObject]];
208
- (void)viewDidUnload
210
[super viewDidUnload];
212
if ([EAGLContext currentContext] == self.context) {
213
[EAGLContext setCurrentContext:nil];
218
- (void)didReceiveMemoryWarning
220
[super didReceiveMemoryWarning];
225
[self viewDidUnload];
227
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
228
if ([GCController class]) {
229
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidConnectNotification object:nil];
230
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidDisconnectNotification object:nil];
231
self.gameController = nil;
234
NativeShutdownGraphics();
235
graphicsContext->Shutdown();
236
delete graphicsContext;
237
graphicsContext = NULL;
241
// For iOS before 6.0
242
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
244
return UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
247
// For iOS 6.0 and up
248
- (NSUInteger)supportedInterfaceOrientations
250
return UIInterfaceOrientationMaskLandscape;
253
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
256
lock_guard guard(input_state.lock);
257
UpdateInputState(&input_state);
258
NativeUpdate(input_state);
259
EndInputState(&input_state);
262
NativeRender(graphicsContext);
266
- (void)touchX:(float)x y:(float)y code:(int)code pointerId:(int)pointerId
268
lock_guard guard(input_state.lock);
270
float scale = [UIScreen mainScreen].scale;
272
if ([[UIScreen mainScreen] respondsToSelector:@selector(nativeScale)]) {
273
scale = [UIScreen mainScreen].nativeScale;
276
float scaledX = (int)(x * dp_xscale) * scale;
277
float scaledY = (int)(y * dp_yscale) * scale;
281
input_state.pointer_x[pointerId] = scaledX;
282
input_state.pointer_y[pointerId] = scaledY;
287
input_state.pointer_down[pointerId] = true;
288
input.flags = TOUCH_DOWN;
292
input_state.pointer_down[pointerId] = false;
293
input.flags = TOUCH_UP;
297
input.flags = TOUCH_MOVE;
300
input_state.mouse_valid = true;
301
input.id = pointerId;
305
- (NSDictionary*)touchDictBy:(UITouch*)touch
307
for (NSDictionary* dict in self.touches) {
308
if ([dict objectForKey:@"touch"] == touch)
314
- (int)freeTouchIndex
318
for (NSDictionary* dict in self.touches)
320
int i = [[dict objectForKey:@"index"] intValue];
328
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
330
for(UITouch* touch in touches)
332
NSDictionary* dict = @{@"touch":touch,@"index":@([self freeTouchIndex])};
333
[self.touches addObject:dict];
334
CGPoint point = [touch locationInView:self.view];
335
[self touchX:point.x y:point.y code:1 pointerId:[[dict objectForKey:@"index"] intValue]];
339
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
341
for(UITouch* touch in touches)
343
CGPoint point = [touch locationInView:self.view];
344
NSDictionary* dict = [self touchDictBy:touch];
345
[self touchX:point.x y:point.y code:0 pointerId:[[dict objectForKey:@"index"] intValue]];
349
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
351
for(UITouch* touch in touches)
353
CGPoint point = [touch locationInView:self.view];
354
NSDictionary* dict = [self touchDictBy:touch];
355
[self touchX:point.x y:point.y code:2 pointerId:[[dict objectForKey:@"index"] intValue]];
356
[self.touches removeObject:dict];
360
- (void)bindDefaultFBO
362
[(GLKView*)self.view bindDrawable];
365
- (void)buttonDown:(iCadeState)button
367
if (simulateAnalog &&
368
((button == iCadeJoystickUp) ||
369
(button == iCadeJoystickDown) ||
370
(button == iCadeJoystickLeft) ||
371
(button == iCadeJoystickRight))) {
374
case iCadeJoystickUp :
375
axis.axisId = JOYSTICK_AXIS_Y;
379
case iCadeJoystickDown :
380
axis.axisId = JOYSTICK_AXIS_Y;
384
case iCadeJoystickLeft :
385
axis.axisId = JOYSTICK_AXIS_X;
389
case iCadeJoystickRight :
390
axis.axisId = JOYSTICK_AXIS_X;
397
axis.deviceId = DEVICE_ID_PAD_0;
402
key.flags = KEY_DOWN;
403
key.keyCode = iCadeToKeyMap[button];
404
key.deviceId = DEVICE_ID_PAD_0;
409
- (void)buttonUp:(iCadeState)button
411
if (button == iCadeButtonA) {
412
// Pressing Select twice within 1 second toggles the DPad between
413
// normal operation and simulating the Analog stick.
414
if ((lastSelectPress + 1.0f) > time_now_d())
415
simulateAnalog = !simulateAnalog;
416
lastSelectPress = time_now_d();
419
if (button == iCadeButtonC) {
420
// Pressing Start twice within 1 second will take to the Emu menu
421
if ((lastStartPress + 1.0f) > time_now_d()) {
423
key.flags = KEY_DOWN;
424
key.keyCode = NKCODE_ESCAPE;
425
key.deviceId = DEVICE_ID_KEYBOARD;
429
lastStartPress = time_now_d();
432
if (simulateAnalog &&
433
((button == iCadeJoystickUp) ||
434
(button == iCadeJoystickDown) ||
435
(button == iCadeJoystickLeft) ||
436
(button == iCadeJoystickRight))) {
439
case iCadeJoystickUp :
440
axis.axisId = JOYSTICK_AXIS_Y;
444
case iCadeJoystickDown :
445
axis.axisId = JOYSTICK_AXIS_Y;
449
case iCadeJoystickLeft :
450
axis.axisId = JOYSTICK_AXIS_X;
454
case iCadeJoystickRight :
455
axis.axisId = JOYSTICK_AXIS_X;
462
axis.deviceId = DEVICE_ID_PAD_0;
468
key.keyCode = iCadeToKeyMap[button];
469
key.deviceId = DEVICE_ID_PAD_0;
475
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1
476
- (void)controllerDidConnect:(NSNotification *)note
478
if (![[GCController controllers] containsObject:self.gameController]) self.gameController = nil;
480
if (self.gameController != nil) return; // already have a connected controller
482
[self setupController:(GCController *)note.object];
485
- (void)controllerDidDisconnect:(NSNotification *)note
487
if (self.gameController == note.object) {
488
self.gameController = nil;
490
if ([[GCController controllers] count] > 0) {
491
[self setupController:[[GCController controllers] firstObject]];
493
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
498
- (void)controllerButtonPressed:(BOOL)pressed keyCode:(keycode_t)keyCode
501
key.deviceId = DEVICE_ID_PAD_0;
502
key.flags = pressed ? KEY_DOWN : KEY_UP;
503
key.keyCode = keyCode;
507
- (void)setupController:(GCController *)controller
509
self.gameController = controller;
511
GCGamepad *baseProfile = self.gameController.gamepad;
512
if (baseProfile == nil) {
513
self.gameController = nil;
517
[[UIApplication sharedApplication] setIdleTimerDisabled:YES]; // prevent auto-lock
519
self.gameController.controllerPausedHandler = ^(GCController *controller) {
521
key.flags = KEY_DOWN;
522
key.keyCode = NKCODE_ESCAPE;
523
key.deviceId = DEVICE_ID_KEYBOARD;
527
baseProfile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
528
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_2]; // Cross
531
baseProfile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
532
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_3]; // Circle
535
baseProfile.buttonX.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
536
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_4]; // Square
539
baseProfile.buttonY.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
540
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_1]; // Triangle
543
baseProfile.leftShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
544
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_7]; // LTrigger
547
baseProfile.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
548
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_8]; // RTrigger
551
baseProfile.dpad.up.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
552
[self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_UP];
555
baseProfile.dpad.down.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
556
[self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_DOWN];
559
baseProfile.dpad.left.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
560
[self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_LEFT];
563
baseProfile.dpad.right.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
564
[self controllerButtonPressed:pressed keyCode:NKCODE_DPAD_RIGHT];
567
GCExtendedGamepad *extendedProfile = self.gameController.extendedGamepad;
568
if (extendedProfile == nil)
569
return; // controller doesn't support extendedGamepad profile
571
extendedProfile.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
572
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_9]; // Select
575
extendedProfile.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
576
[self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_10]; // Start
579
extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
581
axisInput.deviceId = DEVICE_ID_PAD_0;
583
axisInput.axisId = JOYSTICK_AXIS_X;
584
axisInput.value = value;
585
NativeAxis(axisInput);
588
extendedProfile.leftThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
590
axisInput.deviceId = DEVICE_ID_PAD_0;
592
axisInput.axisId = JOYSTICK_AXIS_Y;
593
axisInput.value = -value;
594
NativeAxis(axisInput);
597
// Map right thumbstick as another analog stick, particularly useful for controllers like the DualShock 3/4 when connected to an iOS device
598
extendedProfile.rightThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
600
axisInput.deviceId = DEVICE_ID_PAD_0;
602
axisInput.axisId = JOYSTICK_AXIS_Z;
603
axisInput.value = value;
604
NativeAxis(axisInput);
607
extendedProfile.rightThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
609
axisInput.deviceId = DEVICE_ID_PAD_0;
611
axisInput.axisId = JOYSTICK_AXIS_RZ;
612
axisInput.value = -value;
613
NativeAxis(axisInput);
620
void LaunchBrowser(char const* url)
622
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithCString:url encoding:NSStringEncodingConversionAllowLossy]]];
625
void bindDefaultFBO()
627
[sharedViewController bindDefaultFBO];