src/platform/switch/main.c (view raw)
1/* Copyright (c) 2013-2018 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "feature/gui/gui-runner.h"
7#include <mgba/core/blip_buf.h>
8#include <mgba/core/core.h>
9#include <mgba/internal/gba/audio.h>
10#include <mgba/internal/gba/input.h>
11#include <mgba-util/gui.h>
12#include <mgba-util/gui/font.h>
13#include <mgba-util/gui/menu.h>
14
15#include <switch.h>
16#include <EGL/egl.h>
17#include <GLES3/gl3.h>
18
19#define AUTO_INPUT 0x4E585031
20#define SAMPLES 0x400
21#define BUFFER_SIZE 0x1000
22#define N_BUFFERS 4
23#define ANALOG_DEADZONE 0x4000
24
25TimeType __nx_time_type = TimeType_UserSystemClock;
26
27static EGLDisplay s_display;
28static EGLContext s_context;
29static EGLSurface s_surface;
30
31static const GLfloat _offsets[] = {
32 0.f, 0.f,
33 1.f, 0.f,
34 1.f, 1.f,
35 0.f, 1.f,
36};
37
38static const GLchar* const _gles2Header =
39 "#version 100\n"
40 "precision mediump float;\n";
41
42static const char* const _vertexShader =
43 "attribute vec2 offset;\n"
44 "uniform vec2 dims;\n"
45 "uniform vec2 insize;\n"
46 "varying vec2 texCoord;\n"
47
48 "void main() {\n"
49 " vec2 ratio = insize / 256.0;\n"
50 " vec2 scaledOffset = offset * dims;\n"
51 " gl_Position = vec4(scaledOffset.x * 2.0 - dims.x, scaledOffset.y * -2.0 + dims.y, 0.0, 1.0);\n"
52 " texCoord = offset * ratio;\n"
53 "}";
54
55static const char* const _fragmentShader =
56 "varying vec2 texCoord;\n"
57 "uniform sampler2D tex;\n"
58 "uniform vec4 color;\n"
59
60 "void main() {\n"
61 " vec4 texColor = vec4(texture2D(tex, texCoord).rgb, 1.0);\n"
62 " texColor *= color;\n"
63 " gl_FragColor = texColor;\n"
64 "}";
65
66static GLuint program;
67static GLuint vbo;
68static GLuint vao;
69static GLuint pbo;
70static GLuint texLocation;
71static GLuint dimsLocation;
72static GLuint insizeLocation;
73static GLuint colorLocation;
74static GLuint tex;
75
76static color_t* frameBuffer;
77static struct mAVStream stream;
78static struct mSwitchRumble {
79 struct mRumble d;
80 int up;
81 int down;
82 HidVibrationValue value;
83} rumble;
84static struct mRotationSource rotation = {0};
85static int audioBufferActive;
86static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
87static AudioOutBuffer audoutBuffer[N_BUFFERS];
88static int enqueuedBuffers;
89static bool frameLimiter = true;
90static unsigned framecount = 0;
91static unsigned framecap = 10;
92static u32 vibrationDeviceHandles[4];
93static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
94
95static bool initEgl() {
96 s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
97 if (!s_display) {
98 goto _fail0;
99 }
100
101 eglInitialize(s_display, NULL, NULL);
102
103 EGLConfig config;
104 EGLint numConfigs;
105 static const EGLint attributeList[] = {
106 EGL_RED_SIZE, 1,
107 EGL_GREEN_SIZE, 1,
108 EGL_BLUE_SIZE, 1,
109 EGL_NONE
110 };
111 eglChooseConfig(s_display, attributeList, &config, 1, &numConfigs);
112 if (!numConfigs) {
113 goto _fail1;
114 }
115
116 s_surface = eglCreateWindowSurface(s_display, config, "", NULL);
117 if (!s_surface) {
118 goto _fail1;
119 }
120
121 EGLint contextAttributeList[] = {
122 EGL_CONTEXT_CLIENT_VERSION, 3,
123 EGL_NONE
124 };
125 s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
126 if (!s_context) {
127 goto _fail2;
128 }
129
130 eglMakeCurrent(s_display, s_surface, s_surface, s_context);
131 return true;
132
133_fail2:
134 eglDestroySurface(s_display, s_surface);
135 s_surface = NULL;
136_fail1:
137 eglTerminate(s_display);
138 s_display = NULL;
139_fail0:
140 return false;
141}
142
143static void deinitEgl() {
144 if (s_display) {
145 if (s_context) {
146 eglDestroyContext(s_display, s_context);
147 }
148 if (s_surface) {
149 eglDestroySurface(s_display, s_surface);
150 }
151 eglTerminate(s_display);
152 }
153}
154
155static void _mapKey(struct mInputMap* map, uint32_t binding, int nativeKey, enum GBAKey key) {
156 mInputBindKey(map, binding, __builtin_ctz(nativeKey), key);
157}
158
159static void _drawStart(void) {
160 glClear(GL_COLOR_BUFFER_BIT);
161}
162
163static void _drawEnd(void) {
164 if (frameLimiter || framecount >= framecap) {
165 eglSwapBuffers(s_display, s_surface);
166 framecount = 0;
167 }
168}
169
170static uint32_t _pollInput(const struct mInputMap* map) {
171 int keys = 0;
172 hidScanInput();
173 u32 padkeys = hidKeysHeld(CONTROLLER_P1_AUTO);
174 keys |= mInputMapKeyBits(map, AUTO_INPUT, padkeys, 0);
175
176 JoystickPosition jspos;
177 hidJoystickRead(&jspos, CONTROLLER_P1_AUTO, JOYSTICK_LEFT);
178
179 int l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_LEFT));
180 int r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_RIGHT));
181 int u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_UP));
182 int d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_LSTICK_DOWN));
183
184 if (l == -1) {
185 l = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DLEFT));
186 }
187 if (r == -1) {
188 r = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DRIGHT));
189 }
190 if (u == -1) {
191 u = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DUP));
192 }
193 if (d == -1) {
194 d = mInputMapKey(map, AUTO_INPUT, __builtin_ctz(KEY_DDOWN));
195 }
196
197 if (jspos.dx < -ANALOG_DEADZONE && l != -1) {
198 keys |= 1 << l;
199 }
200 if (jspos.dx > ANALOG_DEADZONE && r != -1) {
201 keys |= 1 << r;
202 }
203 if (jspos.dy < -ANALOG_DEADZONE && d != -1) {
204 keys |= 1 << d;
205 }
206 if (jspos.dy > ANALOG_DEADZONE && u != -1) {
207 keys |= 1 << u;
208 }
209 return keys;
210}
211
212static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
213 hidScanInput();
214 if (hidTouchCount() < 1) {
215 return GUI_CURSOR_NOT_PRESENT;
216 }
217 touchPosition touch;
218 hidTouchRead(&touch, 0);
219 *x = touch.px;
220 *y = touch.py;
221 return GUI_CURSOR_DOWN;
222}
223
224
225static void _setup(struct mGUIRunner* runner) {
226 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_A, GBA_KEY_A);
227 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_B, GBA_KEY_B);
228 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_PLUS, GBA_KEY_START);
229 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_MINUS, GBA_KEY_SELECT);
230 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DUP, GBA_KEY_UP);
231 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DDOWN, GBA_KEY_DOWN);
232 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DLEFT, GBA_KEY_LEFT);
233 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_DRIGHT, GBA_KEY_RIGHT);
234 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_L, GBA_KEY_L);
235 _mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
236
237 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
238 runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
239 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
240 runner->core->setAVStream(runner->core, &stream);
241}
242
243static void _gameLoaded(struct mGUIRunner* runner) {
244 u32 samplerate = audoutGetSampleRate();
245
246 double ratio = GBAAudioCalculateRatio(1, 60.0, 1);
247 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), samplerate * ratio);
248 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
249
250 mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
251
252 rumble.up = 0;
253 rumble.down = 0;
254}
255
256static void _gameUnloaded(struct mGUIRunner* runner) {
257 HidVibrationValue values[4];
258 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
259 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
260 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
261 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
262 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
263}
264
265static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
266 glEnable(GL_BLEND);
267 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
268
269 glUseProgram(program);
270 glBindVertexArray(vao);
271 float aspectX = width / (float) runner->params.width;
272 float aspectY = height / (float) runner->params.height;
273 float max;
274 if (aspectX > aspectY) {
275 max = floor(1.0 / aspectX);
276 } else {
277 max = floor(1.0 / aspectY);
278 }
279
280 aspectX *= max;
281 aspectY *= max;
282
283 glUniform1i(texLocation, 0);
284 glUniform2f(dimsLocation, aspectX, aspectY);
285 glUniform2f(insizeLocation, width, height);
286 if (!faded) {
287 glUniform4f(colorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
288 } else {
289 glUniform4f(colorLocation, 0.8f, 0.8f, 0.8f, 0.8f);
290 }
291
292 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
293
294 glBindVertexArray(0);
295 glUseProgram(0);
296}
297
298static void _prepareForFrame(struct mGUIRunner* runner) {
299 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
300 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
301 if (frameBuffer) {
302 runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
303 }
304 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
305}
306
307static void _drawFrame(struct mGUIRunner* runner, bool faded) {
308 ++framecount;
309 if (!frameLimiter && framecount < framecap) {
310 return;
311 }
312
313 unsigned width, height;
314 runner->core->desiredVideoDimensions(runner->core, &width, &height);
315
316 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
317 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
318
319 glActiveTexture(GL_TEXTURE0);
320 glBindTexture(GL_TEXTURE_2D, tex);
321 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, height, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
322 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
323
324 _drawTex(runner, width, height, faded);
325
326 HidVibrationValue values[4];
327 if (rumble.up) {
328 rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
329 rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
330 memcpy(&values[0], &rumble.value, sizeof(rumble.value));
331 memcpy(&values[1], &rumble.value, sizeof(rumble.value));
332 memcpy(&values[2], &rumble.value, sizeof(rumble.value));
333 memcpy(&values[3], &rumble.value, sizeof(rumble.value));
334 } else {
335 memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
336 memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
337 memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
338 memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
339 }
340 hidSendVibrationValues(vibrationDeviceHandles, values, 4);
341 rumble.up = 0;
342 rumble.down = 0;
343}
344
345static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
346 glActiveTexture(GL_TEXTURE0);
347 glBindTexture(GL_TEXTURE_2D, tex);
348 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
349
350 _drawTex(runner, width, height, faded);
351}
352
353static uint16_t _pollGameInput(struct mGUIRunner* runner) {
354 return _pollInput(&runner->core->inputMap);
355}
356
357static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
358 UNUSED(runner);
359 if (!frameLimiter && limit) {
360 while (enqueuedBuffers > 1) {
361 AudioOutBuffer* releasedBuffers;
362 u32 audoutNReleasedBuffers;
363 audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 100000000);
364 enqueuedBuffers -= audoutNReleasedBuffers;
365 }
366 }
367 frameLimiter = limit;
368}
369
370static bool _running(struct mGUIRunner* runner) {
371 UNUSED(runner);
372 return appletMainLoop();
373}
374
375static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
376 UNUSED(stream);
377 AudioOutBuffer* releasedBuffers;
378 u32 audoutNReleasedBuffers;
379 audoutGetReleasedAudioOutBuffer(&releasedBuffers, &audoutNReleasedBuffers);
380 enqueuedBuffers -= audoutNReleasedBuffers;
381 if (!frameLimiter && enqueuedBuffers >= N_BUFFERS) {
382 blip_clear(left);
383 blip_clear(right);
384 return;
385 }
386 if (enqueuedBuffers >= N_BUFFERS - 1 && R_SUCCEEDED(audoutWaitPlayFinish(&releasedBuffers, &audoutNReleasedBuffers, 10000000))) {
387 enqueuedBuffers -= audoutNReleasedBuffers;
388 }
389
390 struct GBAStereoSample* samples = audioBuffer[audioBufferActive];
391 blip_read_samples(left, &samples[0].left, SAMPLES, true);
392 blip_read_samples(right, &samples[0].right, SAMPLES, true);
393 audoutAppendAudioOutBuffer(&audoutBuffer[audioBufferActive]);
394 audioBufferActive += 1;
395 audioBufferActive %= N_BUFFERS;
396 ++enqueuedBuffers;
397}
398
399void _setRumble(struct mRumble* rumble, int enable) {
400 struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
401 if (enable) {
402 ++sr->up;
403 } else {
404 ++sr->down;
405 }
406}
407
408int32_t _readTiltX(struct mRotationSource* source) {
409 UNUSED(source);
410 SixAxisSensorValues sixaxis;
411 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
412 return sixaxis.accelerometer.x * 3e8f;
413}
414
415int32_t _readTiltY(struct mRotationSource* source) {
416 UNUSED(source);
417 SixAxisSensorValues sixaxis;
418 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
419 return sixaxis.accelerometer.y * -3e8f;
420}
421
422int32_t _readGyroZ(struct mRotationSource* source) {
423 UNUSED(source);
424 SixAxisSensorValues sixaxis;
425 hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
426 return sixaxis.gyroscope.z * 1.1e9f;
427}
428
429static int _batteryState(void) {
430 u32 charge;
431 int state = 0;
432 if (R_SUCCEEDED(psmGetBatteryChargePercentage(&charge))) {
433 state = (charge + 12) / 25;
434 } else {
435 return BATTERY_NOT_PRESENT;
436 }
437 ChargerType type;
438 if (R_SUCCEEDED(psmGetChargerType(&type)) && type) {
439 state |= BATTERY_CHARGING;
440 }
441 return state;
442}
443
444int main(int argc, char* argv[]) {
445 socketInitializeDefault();
446 nxlinkStdio();
447 initEgl();
448 romfsInit();
449 audoutInitialize();
450 psmInitialize();
451
452 struct GUIFont* font = GUIFontCreate();
453
454 u32 width = 1280;
455 u32 height = 720;
456
457 glViewport(0, 0, width, height);
458 glClearColor(0.f, 0.f, 0.f, 1.f);
459
460 glGenTextures(1, &tex);
461 glActiveTexture(GL_TEXTURE0);
462 glBindTexture(GL_TEXTURE_2D, tex);
463 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
464 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
465 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
466 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
467 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
468
469 glGenBuffers(1, &pbo);
470 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
471 glBufferData(GL_PIXEL_UNPACK_BUFFER, 256 * 256 * 4, NULL, GL_STREAM_DRAW);
472 frameBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 256 * 256 * 4, GL_MAP_WRITE_BIT);
473 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
474
475 program = glCreateProgram();
476 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
477 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
478 const GLchar* shaderBuffer[2];
479
480 shaderBuffer[0] = _gles2Header;
481
482 shaderBuffer[1] = _vertexShader;
483 glShaderSource(vertexShader, 2, shaderBuffer, NULL);
484
485 shaderBuffer[1] = _fragmentShader;
486 glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
487
488 glAttachShader(program, vertexShader);
489 glAttachShader(program, fragmentShader);
490
491 glCompileShader(fragmentShader);
492
493 GLint success;
494 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
495 if (!success) {
496 GLchar msg[512];
497 glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
498 puts(msg);
499 }
500
501 glCompileShader(vertexShader);
502
503 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
504 if (!success) {
505 GLchar msg[512];
506 glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
507 puts(msg);
508 }
509 glLinkProgram(program);
510
511 glDeleteShader(vertexShader);
512 glDeleteShader(fragmentShader);
513
514 texLocation = glGetUniformLocation(program, "tex");
515 colorLocation = glGetUniformLocation(program, "color");
516 dimsLocation = glGetUniformLocation(program, "dims");
517 insizeLocation = glGetUniformLocation(program, "insize");
518 GLuint offsetLocation = glGetAttribLocation(program, "offset");
519
520 glGenBuffers(1, &vbo);
521 glGenVertexArrays(1, &vao);
522 glBindVertexArray(vao);
523 glBindBuffer(GL_ARRAY_BUFFER, vbo);
524 glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
525 glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
526 glEnableVertexAttribArray(offsetLocation);
527 glBindVertexArray(0);
528
529 rumble.d.setRumble = _setRumble;
530 rumble.value.freq_low = 120.0;
531 rumble.value.freq_high = 180.0;
532 hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
533 hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
534
535 u32 handles[4];
536 hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
537 hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
538 hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
539 hidStartSixAxisSensor(handles[0]);
540 hidStartSixAxisSensor(handles[1]);
541 hidStartSixAxisSensor(handles[2]);
542 hidStartSixAxisSensor(handles[3]);
543 rotation.readTiltX = _readTiltX;
544 rotation.readTiltY = _readTiltY;
545 rotation.readGyroZ = _readGyroZ;
546
547 stream.videoDimensionsChanged = NULL;
548 stream.postVideoFrame = NULL;
549 stream.postAudioFrame = NULL;
550 stream.postAudioBuffer = _postAudioBuffer;
551
552 memset(audioBuffer, 0, sizeof(audioBuffer));
553 audioBufferActive = 0;
554 enqueuedBuffers = 0;
555 size_t i;
556 for (i = 0; i < N_BUFFERS; ++i) {
557 audoutBuffer[i].next = NULL;
558 audoutBuffer[i].buffer = audioBuffer[i];
559 audoutBuffer[i].buffer_size = BUFFER_SIZE;
560 audoutBuffer[i].data_size = BUFFER_SIZE;
561 audoutBuffer[i].data_offset = 0;
562 }
563
564 struct mGUIRunner runner = {
565 .params = {
566 width, height,
567 font, "/",
568 _drawStart, _drawEnd,
569 _pollInput, _pollCursor,
570 _batteryState,
571 NULL, NULL,
572 },
573 .keySources = (struct GUIInputKeys[]) {
574 {
575 .name = "Controller Input",
576 .id = AUTO_INPUT,
577 .keyNames = (const char*[]) {
578 "A",
579 "B",
580 "X",
581 "Y",
582 "L Stick",
583 "R Stick",
584 "L",
585 "R",
586 "ZL",
587 "ZR",
588 "+",
589 "-",
590 "Left",
591 "Up",
592 "Right",
593 "Down",
594 "L Left",
595 "L Up",
596 "L Right",
597 "L Down",
598 "R Left",
599 "R Up",
600 "R Right",
601 "R Down",
602 "SL",
603 "SR"
604 },
605 .nKeys = 26
606 },
607 { .id = 0 }
608 },
609 .configExtra = (struct GUIMenuItem[]) {
610 {
611 .title = "Fast forward cap",
612 .data = "fastForwardCap",
613 .submenu = 0,
614 .state = 7,
615 .validStates = (const char*[]) {
616 "3", "4", "5", "6", "7", "8", "9",
617 "10", "11", "12", "13", "14", "15",
618 "20", "30"
619 },
620 .stateMappings = (const struct GUIVariant[]) {
621 GUI_V_U(3),
622 GUI_V_U(4),
623 GUI_V_U(5),
624 GUI_V_U(6),
625 GUI_V_U(7),
626 GUI_V_U(8),
627 GUI_V_U(9),
628 GUI_V_U(10),
629 GUI_V_U(11),
630 GUI_V_U(12),
631 GUI_V_U(13),
632 GUI_V_U(14),
633 GUI_V_U(15),
634 GUI_V_U(20),
635 GUI_V_U(30),
636 },
637 .nStates = 15
638 },
639 },
640 .nConfigExtra = 1,
641 .setup = _setup,
642 .teardown = NULL,
643 .gameLoaded = _gameLoaded,
644 .gameUnloaded = _gameUnloaded,
645 .prepareForFrame = _prepareForFrame,
646 .drawFrame = _drawFrame,
647 .drawScreenshot = _drawScreenshot,
648 .paused = _gameUnloaded,
649 .unpaused = _gameLoaded,
650 .incrementScreenMode = NULL,
651 .setFrameLimiter = _setFrameLimiter,
652 .pollGameInput = _pollGameInput,
653 .running = _running
654 };
655 mGUIInit(&runner, "switch");
656
657 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_A, GUI_INPUT_SELECT);
658 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_B, GUI_INPUT_BACK);
659 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_X, GUI_INPUT_CANCEL);
660 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DUP, GUI_INPUT_UP);
661 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DDOWN, GUI_INPUT_DOWN);
662 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DLEFT, GUI_INPUT_LEFT);
663 _mapKey(&runner.params.keyMap, AUTO_INPUT, KEY_DRIGHT, GUI_INPUT_RIGHT);
664
665 audoutStartAudioOut();
666
667 if (argc > 1) {
668 size_t i;
669 for (i = 0; runner.keySources[i].id; ++i) {
670 mInputMapLoad(&runner.params.keyMap, runner.keySources[i].id, mCoreConfigGetInput(&runner.config));
671 }
672 mGUIRun(&runner, argv[1]);
673 } else {
674 mGUIRunloop(&runner);
675 }
676
677 glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
678 glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
679 glDeleteBuffers(1, &pbo);
680
681 glDeleteTextures(1, &tex);
682 glDeleteBuffers(1, &vbo);
683 glDeleteProgram(program);
684 glDeleteVertexArrays(1, &vao);
685
686 hidStopSixAxisSensor(handles[0]);
687 hidStopSixAxisSensor(handles[1]);
688 hidStopSixAxisSensor(handles[2]);
689 hidStopSixAxisSensor(handles[3]);
690
691 psmExit();
692 audoutExit();
693 deinitEgl();
694 socketExit();
695 return 0;
696}