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#include <mgba/core/blip_buf.h>
8#include <mgba/core/core.h>
9#ifdef M_CORE_GBA
10#include <mgba/internal/gba/gba.h>
11#include <mgba/internal/gba/input.h>
12#include <mgba/internal/gba/video.h>
13#endif
14#ifdef M_CORE_GB
15#include <mgba/internal/gb/gb.h>
16#endif
17#include "feature/gui/gui-runner.h"
18#include <mgba-util/gui.h>
19#include <mgba-util/gui/file-select.h>
20#include <mgba-util/gui/font.h>
21#include <mgba-util/gui/menu.h>
22#include <mgba-util/memory.h>
23
24#include <mgba-util/platform/3ds/3ds-vfs.h>
25#include "ctr-gpu.h"
26
27#include <3ds.h>
28#include <3ds/gpu/gx.h>
29
30static enum ScreenMode {
31 SM_PA_BOTTOM,
32 SM_AF_BOTTOM,
33 SM_SF_BOTTOM,
34 SM_PA_TOP,
35 SM_AF_TOP,
36 SM_SF_TOP,
37 SM_MAX
38} screenMode = SM_PA_TOP;
39
40static enum FilterMode {
41 FM_NEAREST,
42 FM_LINEAR_1x,
43 FM_LINEAR_2x,
44 FM_MAX
45} filterMode = FM_LINEAR_2x;
46
47static enum DarkenMode {
48 DM_NATIVE,
49 DM_MULT,
50 DM_MULT_SCALE,
51 DM_MULT_SCALE_BIAS,
52 DM_MAX
53} darkenMode = DM_NATIVE;
54
55#define _3DS_INPUT 0x3344534B
56
57#define AUDIO_SAMPLES 384
58#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
59#define DSP_BUFFERS 4
60
61static struct GBA3DSRotationSource {
62 struct mRotationSource d;
63 accelVector accel;
64 angularRate gyro;
65} rotation;
66
67static enum {
68 NO_SOUND,
69 DSP_SUPPORTED,
70 CSND_SUPPORTED
71} hasSound;
72
73// TODO: Move into context
74static void* outputBuffer;
75static struct mAVStream stream;
76static int16_t* audioLeft = 0;
77static int16_t* audioRight = 0;
78static size_t audioPos = 0;
79static C3D_Tex outputTexture;
80static ndspWaveBuf dspBuffer[DSP_BUFFERS];
81static int bufferId = 0;
82static bool frameLimiter = true;
83static u64 tickCounter;
84
85static C3D_RenderTarget* topScreen[2];
86static C3D_RenderTarget* bottomScreen[2];
87static int doubleBuffer = 0;
88static bool frameStarted = false;
89
90static C3D_RenderTarget* upscaleBuffer;
91static C3D_Tex upscaleBufferTex;
92
93static aptHookCookie cookie;
94
95extern bool allocateRomBuffer(void);
96
97static bool _initGpu(void) {
98 if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) {
99 return false;
100 }
101
102 topScreen[0] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
103 topScreen[1] = C3D_RenderTargetCreate(240, 400, GPU_RB_RGB8, 0);
104 bottomScreen[0] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
105 bottomScreen[1] = C3D_RenderTargetCreate(240, 320, GPU_RB_RGB8, 0);
106 if (!topScreen[0] || !topScreen[1] || !bottomScreen[0] || !bottomScreen[1]) {
107 return false;
108 }
109
110 if (!C3D_TexInitVRAM(&upscaleBufferTex, 512, 512, GPU_RB_RGB8)) {
111 return false;
112 }
113 upscaleBuffer = C3D_RenderTargetCreateFromTex(&upscaleBufferTex, GPU_TEXFACE_2D, 0, 0);
114 if (!upscaleBuffer) {
115 return false;
116 }
117
118 C3D_RenderTargetSetClear(upscaleBuffer, C3D_CLEAR_COLOR, 0, 0);
119
120 return ctrInitGpu();
121}
122
123static void _cleanup(void) {
124 ctrDeinitGpu();
125
126 if (outputBuffer) {
127 linearFree(outputBuffer);
128 }
129
130 C3D_RenderTargetDelete(topScreen[0]);
131 C3D_RenderTargetDelete(topScreen[1]);
132 C3D_RenderTargetDelete(bottomScreen[0]);
133 C3D_RenderTargetDelete(bottomScreen[1]);
134 C3D_RenderTargetDelete(upscaleBuffer);
135 C3D_TexDelete(&upscaleBufferTex);
136 C3D_TexDelete(&outputTexture);
137 C3D_Fini();
138
139 gfxExit();
140
141 if (hasSound != NO_SOUND) {
142 linearFree(audioLeft);
143 }
144
145 if (hasSound == CSND_SUPPORTED) {
146 linearFree(audioRight);
147 csndExit();
148 }
149
150 if (hasSound == DSP_SUPPORTED) {
151 ndspExit();
152 }
153
154 csndExit();
155 ptmuExit();
156}
157
158static void _aptHook(APT_HookType hook, void* user) {
159 UNUSED(user);
160 switch (hook) {
161 case APTHOOK_ONSUSPEND:
162 case APTHOOK_ONSLEEP:
163 if (hasSound == CSND_SUPPORTED) {
164 CSND_SetPlayState(8, 0);
165 CSND_SetPlayState(9, 0);
166 csndExecCmds(false);
167 }
168 break;
169 case APTHOOK_ONEXIT:
170 if (hasSound == CSND_SUPPORTED) {
171 CSND_SetPlayState(8, 0);
172 CSND_SetPlayState(9, 0);
173 csndExecCmds(false);
174 }
175 _cleanup();
176 exit(0);
177 break;
178 default:
179 break;
180 }
181}
182
183static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
184 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
185}
186
187static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
188 u32 pleft = 0, pright = 0;
189
190 int loopMode = (flags >> 10) & 3;
191 if (!loopMode) {
192 flags |= SOUND_ONE_SHOT;
193 }
194
195 pleft = osConvertVirtToPhys(left);
196 pright = osConvertVirtToPhys(right);
197
198 u32 timer = CSND_TIMER(sampleRate);
199 if (timer < 0x0042) {
200 timer = 0x0042;
201 }
202 else if (timer > 0xFFFF) {
203 timer = 0xFFFF;
204 }
205 flags &= ~0xFFFF001F;
206 flags |= SOUND_ENABLE | (timer << 16);
207
208 u32 volumes = CSND_VOL(vol, -1.0);
209 CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
210 volumes = CSND_VOL(vol, 1.0);
211 CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
212}
213
214static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
215
216static void _drawStart(void) {
217}
218
219static void _frameStart(void) {
220 if (frameStarted) {
221 return;
222 }
223 frameStarted = true;
224 u8 flags = 0;
225 if (!frameLimiter) {
226 if (tickCounter + 4481000 > svcGetSystemTick()) {
227 flags = C3D_FRAME_NONBLOCK;
228 } else {
229 tickCounter = svcGetSystemTick();
230 }
231 }
232 C3D_FrameBegin(flags);
233 // Mark both buffers used to make sure they get cleared
234 C3D_FrameDrawOn(topScreen[doubleBuffer]);
235 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
236 ctrStartFrame();
237}
238
239static void _drawEnd(void) {
240 if (!frameStarted) {
241 return;
242 }
243 ctrEndFrame();
244 C3D_RenderTargetSetOutput(topScreen[doubleBuffer], GFX_TOP, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
245 C3D_RenderTargetSetOutput(bottomScreen[doubleBuffer], GFX_BOTTOM, GFX_LEFT, GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8));
246 C3D_FrameEnd(GX_CMDLIST_FLUSH);
247 frameStarted = false;
248
249 doubleBuffer ^= 1;
250 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
251 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
252}
253
254static int _batteryState(void) {
255 u8 charge;
256 u8 adapter;
257 PTMU_GetBatteryLevel(&charge);
258 PTMU_GetBatteryChargeState(&adapter);
259 int state = 0;
260 if (adapter) {
261 state |= BATTERY_CHARGING;
262 }
263 if (charge > 0) {
264 --charge;
265 }
266 return state | charge;
267}
268
269static void _guiPrepare(void) {
270 _frameStart();
271 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
272 ctrSetViewportSize(320, 240, true);
273}
274
275static void _guiFinish(void) {
276 ctrFlushBatch();
277}
278
279static void _setup(struct mGUIRunner* runner) {
280 uint8_t mask;
281 if (R_SUCCEEDED(svcGetProcessAffinityMask(&mask, CUR_PROCESS_HANDLE, 4)) && mask >= 4) {
282 mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
283 mCoreLoadForeignConfig(runner->core, &runner->config);
284 }
285
286 runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation.d);
287 if (hasSound != NO_SOUND) {
288 runner->core->setAVStream(runner->core, &stream);
289 }
290
291 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
292 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
293 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
294 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
295 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
296 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
297 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
298 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
299 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
300 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
301
302 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
303 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
304
305 unsigned mode;
306 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
307 screenMode = mode;
308 }
309 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
310 filterMode = mode;
311 if (filterMode == FM_NEAREST) {
312 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
313 } else {
314 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
315 }
316 }
317 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
318 darkenMode = mode;
319 }
320 frameLimiter = true;
321
322 runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
323}
324
325static void _gameLoaded(struct mGUIRunner* runner) {
326 switch (runner->core->platform(runner->core)) {
327#ifdef M_CORE_GBA
328 case PLATFORM_GBA:
329 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
330 HIDUSER_EnableAccelerometer();
331 }
332 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
333 HIDUSER_EnableGyroscope();
334 }
335 break;
336#endif
337#ifdef M_CORE_GB
338 case PLATFORM_GB:
339 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
340 HIDUSER_EnableAccelerometer();
341 }
342 break;
343#endif
344 default:
345 break;
346 }
347 osSetSpeedupEnable(true);
348
349 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
350 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
351 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
352 if (hasSound != NO_SOUND) {
353 audioPos = 0;
354 }
355 if (hasSound == CSND_SUPPORTED) {
356 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
357 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
358 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
359 csndExecCmds(false);
360 } else if (hasSound == DSP_SUPPORTED) {
361 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
362 }
363 unsigned mode;
364 if (mCoreConfigGetUIntValue(&runner->config, "screenMode", &mode) && mode < SM_MAX) {
365 screenMode = mode;
366 }
367 if (mCoreConfigGetUIntValue(&runner->config, "filterMode", &mode) && mode < FM_MAX) {
368 filterMode = mode;
369 if (filterMode == FM_NEAREST) {
370 C3D_TexSetFilter(&upscaleBufferTex, GPU_NEAREST, GPU_NEAREST);
371 } else {
372 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
373 }
374 }
375 if (mCoreConfigGetUIntValue(&runner->config, "darkenMode", &mode) && mode < DM_MAX) {
376 darkenMode = mode;
377 }
378}
379
380static void _gameUnloaded(struct mGUIRunner* runner) {
381 if (hasSound == CSND_SUPPORTED) {
382 CSND_SetPlayState(8, 0);
383 CSND_SetPlayState(9, 0);
384 csndExecCmds(false);
385 }
386 osSetSpeedupEnable(false);
387 frameLimiter = true;
388
389 switch (runner->core->platform(runner->core)) {
390#ifdef M_CORE_GBA
391 case PLATFORM_GBA:
392 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
393 HIDUSER_DisableAccelerometer();
394 }
395 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
396 HIDUSER_DisableGyroscope();
397 }
398 break;
399#endif
400#ifdef M_CORE_GB
401 case PLATFORM_GB:
402 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
403 HIDUSER_DisableAccelerometer();
404 }
405 break;
406#endif
407 default:
408 break;
409 }
410}
411
412static void _drawTex(struct mCore* core, bool faded) {
413 _frameStart();
414 unsigned screen_w, screen_h;
415 switch (screenMode) {
416 case SM_PA_BOTTOM:
417 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
418 screen_w = 320;
419 screen_h = 240;
420 break;
421 case SM_PA_TOP:
422 C3D_FrameDrawOn(topScreen[doubleBuffer]);
423 screen_w = 400;
424 screen_h = 240;
425 break;
426 default:
427 C3D_FrameDrawOn(upscaleBuffer);
428 screen_w = 512;
429 screen_h = 512;
430 break;
431 }
432
433 unsigned corew, coreh;
434 core->desiredVideoDimensions(core, &corew, &coreh);
435
436 int w = corew;
437 int h = coreh;
438 // Get greatest common divisor
439 while (w != 0) {
440 int temp = h % w;
441 h = w;
442 w = temp;
443 }
444 int gcd = h;
445 unsigned aspectw = corew / gcd;
446 unsigned aspecth = coreh / gcd;
447 int x = 0;
448 int y = 0;
449
450 switch (screenMode) {
451 case SM_PA_TOP:
452 case SM_PA_BOTTOM:
453 w = corew;
454 h = coreh;
455 x = (screen_w - w) / 2;
456 y = (screen_h - h) / 2;
457 ctrSetViewportSize(screen_w, screen_h, true);
458 break;
459 case SM_AF_TOP:
460 case SM_AF_BOTTOM:
461 case SM_SF_TOP:
462 case SM_SF_BOTTOM:
463 default:
464 if (filterMode == FM_LINEAR_1x) {
465 w = corew;
466 h = coreh;
467 } else {
468 w = corew * 2;
469 h = coreh * 2;
470 }
471 ctrSetViewportSize(screen_w, screen_h, false);
472 break;
473 }
474
475 ctrActivateTexture(&outputTexture);
476 u32 color;
477 if (!faded) {
478 color = 0xFFFFFFFF;
479 switch (darkenMode) {
480 case DM_NATIVE:
481 case DM_MAX:
482 break;
483 case DM_MULT_SCALE_BIAS:
484 ctrTextureBias(0x070707);
485 // Fall through
486 case DM_MULT_SCALE:
487 color = 0xFF707070;
488 // Fall through
489 case DM_MULT:
490 ctrTextureMultiply();
491 break;
492 }
493 } else {
494 color = 0xFF484848;
495 switch (darkenMode) {
496 case DM_NATIVE:
497 case DM_MAX:
498 break;
499 case DM_MULT_SCALE_BIAS:
500 ctrTextureBias(0x030303);
501 // Fall through
502 case DM_MULT_SCALE:
503 color = 0xFF303030;
504 // Fall through
505 case DM_MULT:
506 ctrTextureMultiply();
507 break;
508 }
509
510 }
511 ctrAddRectEx(color, x, y, w, h, 0, 0, corew, coreh, 0);
512 ctrFlushBatch();
513
514 corew = w;
515 coreh = h;
516 screen_h = 240;
517 if (screenMode < SM_PA_TOP) {
518 C3D_FrameDrawOn(bottomScreen[doubleBuffer]);
519 screen_w = 320;
520 } else {
521 C3D_FrameDrawOn(topScreen[doubleBuffer]);
522 screen_w = 400;
523 }
524 ctrSetViewportSize(screen_w, screen_h, true);
525
526 switch (screenMode) {
527 default:
528 return;
529 case SM_AF_TOP:
530 case SM_AF_BOTTOM:
531 w = screen_w / aspectw;
532 h = screen_h / aspecth;
533 if (w * aspecth > screen_h) {
534 w = aspectw * h;
535 h = aspecth * h;
536 } else {
537 h = aspecth * w;
538 w = aspectw * w;
539 }
540 break;
541 case SM_SF_TOP:
542 case SM_SF_BOTTOM:
543 w = screen_w;
544 h = screen_h;
545 break;
546 }
547
548 x = (screen_w - w) / 2;
549 y = (screen_h - h) / 2;
550 ctrActivateTexture(&upscaleBufferTex);
551 ctrAddRectEx(0xFFFFFFFF, x, y, w, h, 0, 0, corew, coreh, 0);
552 ctrFlushBatch();
553}
554
555static void _drawFrame(struct mGUIRunner* runner, bool faded) {
556 UNUSED(runner);
557
558 C3D_Tex* tex = &outputTexture;
559
560 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
561 C3D_SafeDisplayTransfer(
562 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
563 tex->data, GX_BUFFER_DIM(256, 256),
564 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
565 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
566 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
567
568 if (hasSound == NO_SOUND) {
569 blip_clear(runner->core->getAudioChannel(runner->core, 0));
570 blip_clear(runner->core->getAudioChannel(runner->core, 1));
571 }
572
573 gspWaitForPPF();
574 _drawTex(runner->core, faded);
575}
576
577static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
578
579 C3D_Tex* tex = &outputTexture;
580
581 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
582
583 unsigned y;
584 for (y = 0; y < height; ++y) {
585 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
586 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
587 }
588
589 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
590 C3D_SafeDisplayTransfer(
591 (u32*) newPixels, GX_BUFFER_DIM(256, height),
592 tex->data, GX_BUFFER_DIM(256, 256),
593 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
594 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
595 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
596 gspWaitForPPF();
597 linearFree(newPixels);
598
599 _drawTex(runner->core, faded);
600}
601
602static uint16_t _pollGameInput(struct mGUIRunner* runner) {
603 UNUSED(runner);
604
605 hidScanInput();
606 uint32_t activeKeys = hidKeysHeld();
607 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
608 keys |= (activeKeys >> 24) & 0xF0;
609 return keys;
610}
611
612static void _incrementScreenMode(struct mGUIRunner* runner) {
613 UNUSED(runner);
614 screenMode = (screenMode + 1) % SM_MAX;
615 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
616
617 C3D_FrameBufClear(&bottomScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
618 C3D_FrameBufClear(&topScreen[doubleBuffer]->frameBuf, C3D_CLEAR_COLOR, 0, 0);
619}
620
621static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
622 UNUSED(runner);
623 if (frameLimiter == limit) {
624 return;
625 }
626 frameLimiter = limit;
627 tickCounter = svcGetSystemTick();
628}
629
630static uint32_t _pollInput(const struct mInputMap* map) {
631 hidScanInput();
632 int activeKeys = hidKeysHeld();
633 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
634}
635
636static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
637 hidScanInput();
638 if (!(hidKeysHeld() & KEY_TOUCH)) {
639 return GUI_CURSOR_NOT_PRESENT;
640 }
641 touchPosition pos;
642 hidTouchRead(&pos);
643 *x = pos.px;
644 *y = pos.py;
645 return GUI_CURSOR_DOWN;
646}
647
648static void _sampleRotation(struct mRotationSource* source) {
649 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
650 // Work around ctrulib getting the entries wrong
651 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
652 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
653}
654
655static int32_t _readTiltX(struct mRotationSource* source) {
656 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
657 return rotation->accel.x << 18L;
658}
659
660static int32_t _readTiltY(struct mRotationSource* source) {
661 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
662 return rotation->accel.y << 18L;
663}
664
665static int32_t _readGyroZ(struct mRotationSource* source) {
666 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
667 return rotation->gyro.y << 18L; // Yes, y
668}
669
670static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
671 UNUSED(stream);
672 if (hasSound == CSND_SUPPORTED) {
673 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
674 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
675 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
676 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
677 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
678 if (audioPos == AUDIO_SAMPLES * 3) {
679 u8 playing = 0;
680 csndIsPlaying(0x8, &playing);
681 if (!playing) {
682 CSND_SetPlayState(0x8, 1);
683 CSND_SetPlayState(0x9, 1);
684 csndExecCmds(false);
685 }
686 }
687 } else if (hasSound == DSP_SUPPORTED) {
688 int startId = bufferId;
689 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
690 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
691 if (bufferId == startId) {
692 blip_clear(left);
693 blip_clear(right);
694 return;
695 }
696 }
697 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
698 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
699 dspBuffer[bufferId].data_pcm16 = tmpBuf;
700 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
701 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
702 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
703 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
704 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
705 }
706}
707
708int main() {
709 rotation.d.sample = _sampleRotation;
710 rotation.d.readTiltX = _readTiltX;
711 rotation.d.readTiltY = _readTiltY;
712 rotation.d.readGyroZ = _readGyroZ;
713
714 stream.videoDimensionsChanged = 0;
715 stream.postVideoFrame = 0;
716 stream.postAudioFrame = 0;
717 stream.postAudioBuffer = _postAudioBuffer;
718
719 if (!allocateRomBuffer()) {
720 return 1;
721 }
722
723 aptHook(&cookie, _aptHook, 0);
724
725 ptmuInit();
726 hasSound = NO_SOUND;
727 if (!ndspInit()) {
728 hasSound = DSP_SUPPORTED;
729 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
730 ndspSetOutputCount(1);
731 ndspChnReset(0);
732 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
733 ndspChnSetInterp(0, NDSP_INTERP_NONE);
734 ndspChnSetRate(0, 0x8000);
735 ndspChnWaveBufClear(0);
736 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
737 memset(dspBuffer, 0, sizeof(dspBuffer));
738 int i;
739 for (i = 0; i < DSP_BUFFERS; ++i) {
740 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
741 dspBuffer[i].nsamples = AUDIO_SAMPLES;
742 }
743 }
744
745 if (hasSound == NO_SOUND && !csndInit()) {
746 hasSound = CSND_SUPPORTED;
747 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
748 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
749 }
750
751 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
752
753 if (!_initGpu()) {
754 outputTexture.data = 0;
755 _cleanup();
756 return 1;
757 }
758
759 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
760 _cleanup();
761 return 1;
762 }
763 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
764 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
765 C3D_TexSetFilter(&upscaleBufferTex, GPU_LINEAR, GPU_LINEAR);
766 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
767
768 // Zero texture data to make sure no garbage around the border interferes with filtering
769 GX_MemoryFill(
770 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
771 NULL, 0, NULL, 0);
772 gspWaitForPSC0();
773
774 struct GUIFont* font = GUIFontCreate();
775
776 if (!font) {
777 _cleanup();
778 return 1;
779 }
780
781 struct mGUIRunner runner = {
782 .params = {
783 320, 240,
784 font, "/",
785 _drawStart, _drawEnd,
786 _pollInput, _pollCursor,
787 _batteryState,
788 _guiPrepare, _guiFinish,
789 },
790 .keySources = (struct GUIInputKeys[]) {
791 {
792 .name = "3DS Input",
793 .id = _3DS_INPUT,
794 .keyNames = (const char*[]) {
795 "A",
796 "B",
797 "Select",
798 "Start",
799 "D-Pad Right",
800 "D-Pad Left",
801 "D-Pad Up",
802 "D-Pad Down",
803 "R",
804 "L",
805 "X",
806 "Y",
807 0,
808 0,
809 "ZL",
810 "ZR",
811 0,
812 0,
813 0,
814 0,
815 0,
816 0,
817 0,
818 0,
819 "C-Stick Right",
820 "C-Stick Left",
821 "C-Stick Up",
822 "C-Stick Down",
823 },
824 .nKeys = 28
825 },
826 { .id = 0 }
827 },
828 .configExtra = (struct GUIMenuItem[]) {
829 {
830 .title = "Screen mode",
831 .data = "screenMode",
832 .submenu = 0,
833 .state = SM_PA_TOP,
834 .validStates = (const char*[]) {
835 "Pixel-Accurate/Bottom",
836 "Aspect-Ratio Fit/Bottom",
837 "Stretched/Bottom",
838 "Pixel-Accurate/Top",
839 "Aspect-Ratio Fit/Top",
840 "Stretched/Top",
841 },
842 .nStates = 6
843 },
844 {
845 .title = "Filtering",
846 .data = "filterMode",
847 .submenu = 0,
848 .state = FM_LINEAR_2x,
849 .validStates = (const char*[]) {
850 NULL, // Disable choosing nearest neighbor; it always looks bad
851 "Bilinear (smoother)",
852 "Bilinear (pixelated)",
853 },
854 .nStates = 3
855 },
856 {
857 .title = "Screen darkening",
858 .data = "darkenMode",
859 .submenu = 0,
860 .state = DM_NATIVE,
861 .validStates = (const char*[]) {
862 "None",
863 "Dark",
864 "Very dark",
865 "Grayed",
866 },
867 .nStates = 4
868 }
869 },
870 .nConfigExtra = 3,
871 .setup = _setup,
872 .teardown = 0,
873 .gameLoaded = _gameLoaded,
874 .gameUnloaded = _gameUnloaded,
875 .prepareForFrame = 0,
876 .drawFrame = _drawFrame,
877 .drawScreenshot = _drawScreenshot,
878 .paused = _gameUnloaded,
879 .unpaused = _gameLoaded,
880 .incrementScreenMode = _incrementScreenMode,
881 .setFrameLimiter = _setFrameLimiter,
882 .pollGameInput = _pollGameInput
883 };
884
885 mGUIInit(&runner, "3ds");
886
887 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
888 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
889 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
890 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
891 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
892 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
893 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
894 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
895 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
896 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
897
898 mGUIRunloop(&runner);
899 mGUIDeinit(&runner);
900
901 _cleanup();
902 return 0;
903}