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