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