src/platform/3ds/main.c (view raw)
1/* Copyright (c) 2013-2015 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
7#ifdef M_CORE_GBA
8#include "gba/gba.h"
9#include "gba/input.h"
10#include "gba/video.h"
11#endif
12#ifdef M_CORE_GB
13#include "gb/gb.h"
14#endif
15#include "feature/gui/gui-runner.h"
16#include "util/gui.h"
17#include "util/gui/file-select.h"
18#include "util/gui/font.h"
19#include "util/gui/menu.h"
20#include "util/memory.h"
21
22#include "3ds-vfs.h"
23#include "ctr-gpu.h"
24
25#include <3ds.h>
26#include <3ds/gpu/gx.h>
27
28static enum ScreenMode {
29 SM_PA_BOTTOM,
30 SM_AF_BOTTOM,
31 SM_SF_BOTTOM,
32 SM_PA_TOP,
33 SM_AF_TOP,
34 SM_SF_TOP,
35 SM_MAX
36} screenMode = SM_PA_TOP;
37
38#define _3DS_INPUT 0x3344534B
39
40#define AUDIO_SAMPLES 384
41#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
42#define DSP_BUFFERS 4
43
44FS_Archive sdmcArchive;
45
46static struct GBA3DSRotationSource {
47 struct mRotationSource d;
48 accelVector accel;
49 angularRate gyro;
50} rotation;
51
52static enum {
53 NO_SOUND,
54 DSP_SUPPORTED,
55 CSND_SUPPORTED
56} hasSound;
57
58// TODO: Move into context
59static void* outputBuffer;
60static struct mAVStream stream;
61static int16_t* audioLeft = 0;
62static int16_t* audioRight = 0;
63static size_t audioPos = 0;
64static C3D_Tex outputTexture;
65static ndspWaveBuf dspBuffer[DSP_BUFFERS];
66static int bufferId = 0;
67
68static C3D_RenderBuf bottomScreen;
69static C3D_RenderBuf topScreen;
70
71static aptHookCookie cookie;
72
73extern bool allocateRomBuffer(void);
74
75static bool _initGpu(void) {
76 if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
77 return false;
78 }
79
80 if (!C3D_RenderBufInit(&topScreen, 240, 400, GPU_RB_RGB8, 0) || !C3D_RenderBufInit(&bottomScreen, 240, 320, GPU_RB_RGB8, 0)) {
81 return false;
82 }
83
84 return ctrInitGpu();
85}
86
87static void _cleanup(void) {
88 ctrDeinitGpu();
89
90 if (outputBuffer) {
91 linearFree(outputBuffer);
92 }
93
94 C3D_RenderBufDelete(&topScreen);
95 C3D_RenderBufDelete(&bottomScreen);
96 C3D_Fini();
97
98 gfxExit();
99
100 if (hasSound != NO_SOUND) {
101 linearFree(audioLeft);
102 }
103
104 if (hasSound == CSND_SUPPORTED) {
105 linearFree(audioRight);
106 csndExit();
107 }
108
109 if (hasSound == DSP_SUPPORTED) {
110 ndspExit();
111 }
112
113 csndExit();
114 ptmuExit();
115}
116
117static void _aptHook(APT_HookType hook, void* user) {
118 UNUSED(user);
119 switch (hook) {
120 case APTHOOK_ONSUSPEND:
121 case APTHOOK_ONSLEEP:
122 if (hasSound == CSND_SUPPORTED) {
123 CSND_SetPlayState(8, 0);
124 CSND_SetPlayState(9, 0);
125 csndExecCmds(false);
126 }
127 break;
128 case APTHOOK_ONEXIT:
129 if (hasSound == CSND_SUPPORTED) {
130 CSND_SetPlayState(8, 0);
131 CSND_SetPlayState(9, 0);
132 csndExecCmds(false);
133 }
134 _cleanup();
135 exit(0);
136 break;
137 default:
138 break;
139 }
140}
141
142static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
143 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
144}
145
146static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
147 u32 pleft = 0, pright = 0;
148
149 int loopMode = (flags >> 10) & 3;
150 if (!loopMode) {
151 flags |= SOUND_ONE_SHOT;
152 }
153
154 pleft = osConvertVirtToPhys(left);
155 pright = osConvertVirtToPhys(right);
156
157 u32 timer = CSND_TIMER(sampleRate);
158 if (timer < 0x0042) {
159 timer = 0x0042;
160 }
161 else if (timer > 0xFFFF) {
162 timer = 0xFFFF;
163 }
164 flags &= ~0xFFFF001F;
165 flags |= SOUND_ENABLE | (timer << 16);
166
167 u32 volumes = CSND_VOL(vol, -1.0);
168 CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
169 volumes = CSND_VOL(vol, 1.0);
170 CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
171}
172
173static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
174
175static void _drawStart(void) {
176 C3D_RenderBufClear(&bottomScreen);
177 C3D_RenderBufClear(&topScreen);
178}
179
180static void _drawEnd(void) {
181 ctrFinalize();
182 C3D_RenderBufTransfer(&topScreen, (u32*) gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
183 C3D_RenderBufTransfer(&bottomScreen, (u32*) gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
184 gfxSwapBuffersGpu();
185 gspWaitForEvent(GSPGPU_EVENT_VBlank0, false);
186}
187
188static int _batteryState(void) {
189 u8 charge;
190 u8 adapter;
191 PTMU_GetBatteryLevel(&charge);
192 PTMU_GetBatteryChargeState(&adapter);
193 int state = 0;
194 if (adapter) {
195 state |= BATTERY_CHARGING;
196 }
197 if (charge > 0) {
198 --charge;
199 }
200 return state | charge;
201}
202
203static void _guiPrepare(void) {
204 int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
205 if (screen == GFX_BOTTOM) {
206 return;
207 }
208
209 C3D_RenderBufBind(&bottomScreen);
210 ctrSetViewportSize(320, 240);
211}
212
213static void _guiFinish(void) {
214 ctrFlushBatch();
215}
216
217static void _setup(struct mGUIRunner* runner) {
218 bool isNew3DS = false;
219 APT_CheckNew3DS(&isNew3DS);
220 if (isNew3DS && !envIsHomebrew()) {
221 mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
222 mCoreLoadForeignConfig(runner->core, &runner->config);
223 }
224
225 runner->core->setRotation(runner->core, &rotation.d);
226 if (hasSound != NO_SOUND) {
227 runner->core->setAVStream(runner->core, &stream);
228 }
229
230 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
231 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
232 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
233 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
234 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
235 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
236 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
237 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
238 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
239 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
240
241 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
242 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
243
244 unsigned mode;
245 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
246 screenMode = mode;
247 }
248
249 runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
250}
251
252static void _gameLoaded(struct mGUIRunner* runner) {
253 switch (runner->core->platform(runner->core)) {
254#ifdef M_CORE_GBA
255 case PLATFORM_GBA:
256 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
257 HIDUSER_EnableAccelerometer();
258 }
259 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
260 HIDUSER_EnableGyroscope();
261 }
262 break;
263#endif
264#ifdef M_CORE_GB
265 case PLATFORM_GB:
266 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
267 HIDUSER_EnableAccelerometer();
268 }
269 break;
270#endif
271 default:
272 break;
273 }
274 osSetSpeedupEnable(true);
275
276 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
277 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
278 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
279 if (hasSound != NO_SOUND) {
280 audioPos = 0;
281 }
282 if (hasSound == CSND_SUPPORTED) {
283 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
284 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
285 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
286 csndExecCmds(false);
287 } else if (hasSound == DSP_SUPPORTED) {
288 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
289 }
290 unsigned mode;
291 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode != screenMode) {
292 screenMode = mode;
293 }
294}
295
296static void _gameUnloaded(struct mGUIRunner* runner) {
297 if (hasSound == CSND_SUPPORTED) {
298 CSND_SetPlayState(8, 0);
299 CSND_SetPlayState(9, 0);
300 csndExecCmds(false);
301 }
302 osSetSpeedupEnable(false);
303
304 switch (runner->core->platform(runner->core)) {
305#ifdef M_CORE_GBA
306 case PLATFORM_GBA:
307 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
308 HIDUSER_DisableAccelerometer();
309 }
310 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
311 HIDUSER_DisableGyroscope();
312 }
313 break;
314#endif
315#ifdef M_CORE_GB
316 case PLATFORM_GB:
317 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
318 HIDUSER_DisableAccelerometer();
319 }
320 break;
321#endif
322 default:
323 break;
324 }
325}
326
327static void _drawTex(struct mCore* core, bool faded) {
328 if (screenMode < SM_PA_TOP) {
329 C3D_RenderBufBind(&bottomScreen);
330 ctrSetViewportSize(320, 240);
331 } else {
332 C3D_RenderBufBind(&topScreen);
333 ctrSetViewportSize(400, 240);
334 }
335 ctrActivateTexture(&outputTexture);
336
337 u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF;
338
339 int screen_w = screenMode < SM_PA_TOP ? 320 : 400;
340 int screen_h = 240;
341
342 unsigned corew, coreh;
343 core->desiredVideoDimensions(core, &corew, &coreh);
344
345 int w = corew;
346 int h = coreh;
347 // Get greatest common divisor
348 while (w != 0) {
349 int temp = h % w;
350 h = w;
351 w = temp;
352 }
353 int gcd = h;
354 int aspectw = corew / gcd;
355 int aspecth = coreh / gcd;
356
357 switch (screenMode) {
358 case SM_PA_TOP:
359 case SM_PA_BOTTOM:
360 default:
361 w = corew;
362 h = coreh;
363 break;
364 case SM_AF_TOP:
365 case SM_AF_BOTTOM:
366 w = screen_w / aspectw;
367 h = screen_h / aspecth;
368 if (w * aspecth > screen_h) {
369 w = aspectw * h;
370 h = aspecth * h;
371 } else {
372 h = aspecth * w;
373 w = aspectw * w;
374 }
375 break;
376 case SM_SF_TOP:
377 case SM_SF_BOTTOM:
378 w = screen_w;
379 h = screen_h;
380 break;
381 }
382
383 int x = (screen_w - w) / 2;
384 int y = (screen_h - h) / 2;
385
386 ctrAddRectScaled(color, x, y, w, h, 0, 0, corew, coreh);
387 ctrFlushBatch();
388}
389
390static void _drawFrame(struct mGUIRunner* runner, bool faded) {
391 UNUSED(runner);
392
393 C3D_Tex* tex = &outputTexture;
394
395 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
396 C3D_SafeDisplayTransfer(
397 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
398 tex->data, GX_BUFFER_DIM(256, 256),
399 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
400 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
401 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
402
403 if (hasSound == NO_SOUND) {
404 blip_clear(runner->core->getAudioChannel(runner->core, 0));
405 blip_clear(runner->core->getAudioChannel(runner->core, 1));
406 }
407
408 gspWaitForPPF();
409 _drawTex(runner->core, faded);
410}
411
412static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, unsigned width, unsigned height, bool faded) {
413
414 C3D_Tex* tex = &outputTexture;
415
416 u16* newPixels = linearMemAlign(256 * height * sizeof(u32), 0x100);
417
418 // Convert image from RGBX8 to BGR565
419 for (unsigned y = 0; y < height; ++y) {
420 for (unsigned x = 0; x < width; ++x) {
421 // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
422 u32 p = *pixels++;
423 newPixels[y * 256 + x] =
424 (p << 24 >> (24 + 3) << 11) | // R
425 (p << 16 >> (24 + 2) << 5) | // G
426 (p << 8 >> (24 + 3) << 0); // B
427 }
428 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(u32));
429 }
430
431 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
432 C3D_SafeDisplayTransfer(
433 (u32*) newPixels, GX_BUFFER_DIM(256, height),
434 tex->data, GX_BUFFER_DIM(256, 256),
435 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
436 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
437 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
438 gspWaitForPPF();
439 linearFree(newPixels);
440
441 _drawTex(runner->core, faded);
442}
443
444static uint16_t _pollGameInput(struct mGUIRunner* runner) {
445 UNUSED(runner);
446
447 hidScanInput();
448 uint32_t activeKeys = hidKeysHeld();
449 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
450 keys |= (activeKeys >> 24) & 0xF0;
451 return keys;
452}
453
454static void _incrementScreenMode(struct mGUIRunner* runner) {
455 UNUSED(runner);
456 screenMode = (screenMode + 1) % SM_MAX;
457 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
458
459 C3D_RenderBufClear(&bottomScreen);
460 C3D_RenderBufClear(&topScreen);
461}
462
463static uint32_t _pollInput(const struct mInputMap* map) {
464 hidScanInput();
465 int activeKeys = hidKeysHeld();
466 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
467}
468
469static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
470 hidScanInput();
471 if (!(hidKeysHeld() & KEY_TOUCH)) {
472 return GUI_CURSOR_NOT_PRESENT;
473 }
474 touchPosition pos;
475 hidTouchRead(&pos);
476 *x = pos.px;
477 *y = pos.py;
478 return GUI_CURSOR_DOWN;
479}
480
481static void _sampleRotation(struct mRotationSource* source) {
482 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
483 // Work around ctrulib getting the entries wrong
484 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
485 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
486}
487
488static int32_t _readTiltX(struct mRotationSource* source) {
489 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
490 return rotation->accel.x << 18L;
491}
492
493static int32_t _readTiltY(struct mRotationSource* source) {
494 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
495 return rotation->accel.y << 18L;
496}
497
498static int32_t _readGyroZ(struct mRotationSource* source) {
499 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
500 return rotation->gyro.y << 18L; // Yes, y
501}
502
503static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
504 UNUSED(stream);
505 if (hasSound == CSND_SUPPORTED) {
506 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
507 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
508 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
509 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
510 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
511 if (audioPos == AUDIO_SAMPLES * 3) {
512 u8 playing = 0;
513 csndIsPlaying(0x8, &playing);
514 if (!playing) {
515 CSND_SetPlayState(0x8, 1);
516 CSND_SetPlayState(0x9, 1);
517 csndExecCmds(false);
518 }
519 }
520 } else if (hasSound == DSP_SUPPORTED) {
521 int startId = bufferId;
522 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
523 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
524 if (bufferId == startId) {
525 blip_clear(left);
526 blip_clear(right);
527 return;
528 }
529 }
530 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
531 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
532 dspBuffer[bufferId].data_pcm16 = tmpBuf;
533 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
534 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
535 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
536 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
537 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
538 }
539}
540
541int main() {
542 rotation.d.sample = _sampleRotation;
543 rotation.d.readTiltX = _readTiltX;
544 rotation.d.readTiltY = _readTiltY;
545 rotation.d.readGyroZ = _readGyroZ;
546
547 stream.videoDimensionsChanged = 0;
548 stream.postVideoFrame = 0;
549 stream.postAudioFrame = 0;
550 stream.postAudioBuffer = _postAudioBuffer;
551
552 if (!allocateRomBuffer()) {
553 return 1;
554 }
555
556 aptHook(&cookie, _aptHook, 0);
557
558 ptmuInit();
559 hasSound = NO_SOUND;
560 if (!ndspInit()) {
561 hasSound = DSP_SUPPORTED;
562 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
563 ndspSetOutputCount(1);
564 ndspChnReset(0);
565 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
566 ndspChnSetInterp(0, NDSP_INTERP_NONE);
567 ndspChnSetRate(0, 0x8000);
568 ndspChnWaveBufClear(0);
569 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
570 memset(dspBuffer, 0, sizeof(dspBuffer));
571 int i;
572 for (i = 0; i < DSP_BUFFERS; ++i) {
573 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
574 dspBuffer[i].nsamples = AUDIO_SAMPLES;
575 }
576 }
577
578 if (hasSound == NO_SOUND && !csndInit()) {
579 hasSound = CSND_SUPPORTED;
580 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
581 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
582 }
583
584 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
585
586 if (!_initGpu()) {
587 outputTexture.data = 0;
588 _cleanup();
589 return 1;
590 }
591
592 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
593 _cleanup();
594 return 1;
595 }
596 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
597 C3D_TexSetFilter(&outputTexture, GPU_LINEAR, GPU_LINEAR);
598 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
599
600 // Zero texture data to make sure no garbage around the border interferes with filtering
601 GX_MemoryFill(
602 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
603 NULL, 0, NULL, 0);
604 gspWaitForPSC0();
605
606 FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""));
607
608 struct GUIFont* font = GUIFontCreate();
609
610 if (!font) {
611 _cleanup();
612 return 1;
613 }
614
615 struct mGUIRunner runner = {
616 .params = {
617 320, 240,
618 font, "/",
619 _drawStart, _drawEnd,
620 _pollInput, _pollCursor,
621 _batteryState,
622 _guiPrepare, _guiFinish,
623
624 GUI_PARAMS_TRAIL
625 },
626 .keySources = (struct GUIInputKeys[]) {
627 {
628 .name = "3DS Input",
629 .id = _3DS_INPUT,
630 .keyNames = (const char*[]) {
631 "A",
632 "B",
633 "Select",
634 "Start",
635 "D-Pad Right",
636 "D-Pad Left",
637 "D-Pad Up",
638 "D-Pad Down",
639 "R",
640 "L",
641 "X",
642 "Y",
643 0,
644 0,
645 "ZL",
646 "ZR",
647 0,
648 0,
649 0,
650 0,
651 0,
652 0,
653 0,
654 0,
655 "C-Stick Right",
656 "C-Stick Left",
657 "C-Stick Up",
658 "C-Stick Down",
659 },
660 .nKeys = 28
661 },
662 { .id = 0 }
663 },
664 .configExtra = (struct GUIMenuItem[]) {
665 {
666 .title = "Screen mode",
667 .data = "screenMode",
668 .submenu = 0,
669 .state = SM_PA_TOP,
670 .validStates = (const char*[]) {
671 "Pixel-Accurate/Bottom",
672 "Aspect-Ratio Fit/Bottom",
673 "Stretched/Bottom",
674 "Pixel-Accurate/Top",
675 "Aspect-Ratio Fit/Top",
676 "Stretched/Top",
677 },
678 .nStates = 6
679 }
680 },
681 .nConfigExtra = 1,
682 .setup = _setup,
683 .teardown = 0,
684 .gameLoaded = _gameLoaded,
685 .gameUnloaded = _gameUnloaded,
686 .prepareForFrame = 0,
687 .drawFrame = _drawFrame,
688 .drawScreenshot = _drawScreenshot,
689 .paused = _gameUnloaded,
690 .unpaused = _gameLoaded,
691 .incrementScreenMode = _incrementScreenMode,
692 .pollGameInput = _pollGameInput
693 };
694
695 mGUIInit(&runner, "3ds");
696
697 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
698 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
699 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
700 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
701 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
702 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
703 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
704 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
705 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
706 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
707
708 mGUIRunloop(&runner);
709 mGUIDeinit(&runner);
710
711 _cleanup();
712 return 0;
713}