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