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