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