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 color_t* pixels, unsigned width, unsigned height, bool faded) {
536
537 C3D_Tex* tex = &outputTexture;
538
539 color_t* newPixels = linearMemAlign(256 * height * sizeof(color_t), 0x100);
540
541 unsigned y;
542 for (y = 0; y < height; ++y) {
543 memcpy(&newPixels[y * 256], &pixels[y * width], width * sizeof(color_t));
544 memset(&newPixels[y * 256 + width], 0, (256 - width) * sizeof(color_t));
545 }
546
547 GSPGPU_FlushDataCache(newPixels, 256 * height * sizeof(u32));
548 C3D_SafeDisplayTransfer(
549 (u32*) newPixels, GX_BUFFER_DIM(256, height),
550 tex->data, GX_BUFFER_DIM(256, 256),
551 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
552 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
553 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
554 gspWaitForPPF();
555 linearFree(newPixels);
556
557 _drawTex(runner->core, faded);
558}
559
560static uint16_t _pollGameInput(struct mGUIRunner* runner) {
561 UNUSED(runner);
562
563 hidScanInput();
564 uint32_t activeKeys = hidKeysHeld();
565 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
566 keys |= (activeKeys >> 24) & 0xF0;
567 return keys;
568}
569
570static void _incrementScreenMode(struct mGUIRunner* runner) {
571 UNUSED(runner);
572 screenMode = (screenMode + 1) % SM_MAX;
573 mCoreConfigSetUIntValue(&runner->config, "screenMode", screenMode);
574
575 C3D_RenderBufClear(&bottomScreen);
576 C3D_RenderBufClear(&topScreen);
577}
578
579static void _setFrameLimiter(struct mGUIRunner* runner, bool limit) {
580 UNUSED(runner);
581 frameLimiter = limit;
582}
583
584static uint32_t _pollInput(const struct mInputMap* map) {
585 hidScanInput();
586 int activeKeys = hidKeysHeld();
587 return mInputMapKeyBits(map, _3DS_INPUT, activeKeys, 0);
588}
589
590static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
591 hidScanInput();
592 if (!(hidKeysHeld() & KEY_TOUCH)) {
593 return GUI_CURSOR_NOT_PRESENT;
594 }
595 touchPosition pos;
596 hidTouchRead(&pos);
597 *x = pos.px;
598 *y = pos.py;
599 return GUI_CURSOR_DOWN;
600}
601
602static void _sampleRotation(struct mRotationSource* source) {
603 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
604 // Work around ctrulib getting the entries wrong
605 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
606 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
607}
608
609static int32_t _readTiltX(struct mRotationSource* source) {
610 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
611 return rotation->accel.x << 18L;
612}
613
614static int32_t _readTiltY(struct mRotationSource* source) {
615 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
616 return rotation->accel.y << 18L;
617}
618
619static int32_t _readGyroZ(struct mRotationSource* source) {
620 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
621 return rotation->gyro.y << 18L; // Yes, y
622}
623
624static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
625 UNUSED(stream);
626 if (hasSound == CSND_SUPPORTED) {
627 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
628 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
629 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
630 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
631 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
632 if (audioPos == AUDIO_SAMPLES * 3) {
633 u8 playing = 0;
634 csndIsPlaying(0x8, &playing);
635 if (!playing) {
636 CSND_SetPlayState(0x8, 1);
637 CSND_SetPlayState(0x9, 1);
638 csndExecCmds(false);
639 }
640 }
641 } else if (hasSound == DSP_SUPPORTED) {
642 int startId = bufferId;
643 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
644 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
645 if (bufferId == startId) {
646 blip_clear(left);
647 blip_clear(right);
648 return;
649 }
650 }
651 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
652 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
653 dspBuffer[bufferId].data_pcm16 = tmpBuf;
654 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
655 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
656 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
657 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
658 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
659 }
660}
661
662int main() {
663 rotation.d.sample = _sampleRotation;
664 rotation.d.readTiltX = _readTiltX;
665 rotation.d.readTiltY = _readTiltY;
666 rotation.d.readGyroZ = _readGyroZ;
667
668 stream.videoDimensionsChanged = 0;
669 stream.postVideoFrame = 0;
670 stream.postAudioFrame = 0;
671 stream.postAudioBuffer = _postAudioBuffer;
672
673 if (!allocateRomBuffer()) {
674 return 1;
675 }
676
677 aptHook(&cookie, _aptHook, 0);
678
679 ptmuInit();
680 hasSound = NO_SOUND;
681 if (!ndspInit()) {
682 hasSound = DSP_SUPPORTED;
683 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
684 ndspSetOutputCount(1);
685 ndspChnReset(0);
686 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
687 ndspChnSetInterp(0, NDSP_INTERP_NONE);
688 ndspChnSetRate(0, 0x8000);
689 ndspChnWaveBufClear(0);
690 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
691 memset(dspBuffer, 0, sizeof(dspBuffer));
692 int i;
693 for (i = 0; i < DSP_BUFFERS; ++i) {
694 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
695 dspBuffer[i].nsamples = AUDIO_SAMPLES;
696 }
697 }
698
699 if (hasSound == NO_SOUND && !csndInit()) {
700 hasSound = CSND_SUPPORTED;
701 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
702 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
703 }
704
705 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
706
707 if (!_initGpu()) {
708 outputTexture.data = 0;
709 _cleanup();
710 return 1;
711 }
712
713 if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
714 _cleanup();
715 return 1;
716 }
717 C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
718 C3D_TexSetFilter(&outputTexture, GPU_NEAREST, GPU_NEAREST);
719 C3D_TexSetFilter(&upscaleBuffer.colorBuf, GPU_LINEAR, GPU_LINEAR);
720 void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
721
722 // Zero texture data to make sure no garbage around the border interferes with filtering
723 GX_MemoryFill(
724 outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
725 NULL, 0, NULL, 0);
726 gspWaitForPSC0();
727
728 FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""));
729
730 struct GUIFont* font = GUIFontCreate();
731
732 if (!font) {
733 _cleanup();
734 return 1;
735 }
736
737 struct mGUIRunner runner = {
738 .params = {
739 320, 240,
740 font, "/",
741 _drawStart, _drawEnd,
742 _pollInput, _pollCursor,
743 _batteryState,
744 _guiPrepare, _guiFinish,
745
746 GUI_PARAMS_TRAIL
747 },
748 .keySources = (struct GUIInputKeys[]) {
749 {
750 .name = "3DS Input",
751 .id = _3DS_INPUT,
752 .keyNames = (const char*[]) {
753 "A",
754 "B",
755 "Select",
756 "Start",
757 "D-Pad Right",
758 "D-Pad Left",
759 "D-Pad Up",
760 "D-Pad Down",
761 "R",
762 "L",
763 "X",
764 "Y",
765 0,
766 0,
767 "ZL",
768 "ZR",
769 0,
770 0,
771 0,
772 0,
773 0,
774 0,
775 0,
776 0,
777 "C-Stick Right",
778 "C-Stick Left",
779 "C-Stick Up",
780 "C-Stick Down",
781 },
782 .nKeys = 28
783 },
784 { .id = 0 }
785 },
786 .configExtra = (struct GUIMenuItem[]) {
787 {
788 .title = "Screen mode",
789 .data = "screenMode",
790 .submenu = 0,
791 .state = SM_PA_TOP,
792 .validStates = (const char*[]) {
793 "Pixel-Accurate/Bottom",
794 "Aspect-Ratio Fit/Bottom",
795 "Stretched/Bottom",
796 "Pixel-Accurate/Top",
797 "Aspect-Ratio Fit/Top",
798 "Stretched/Top",
799 },
800 .nStates = 6
801 },
802 {
803 .title = "Filtering",
804 .data = "filterMode",
805 .submenu = 0,
806 .state = FM_LINEAR_2x,
807 .validStates = (const char*[]) {
808 NULL, // Disable choosing nearest neighbor; it always looks bad
809 "Bilinear (smoother)",
810 "Bilinear (pixelated)",
811 },
812 .nStates = 3
813 },
814 {
815 .title = "Screen darkening",
816 .data = "darkenMode",
817 .submenu = 0,
818 .state = DM_NATIVE,
819 .validStates = (const char*[]) {
820 "None",
821 "Dark",
822 "Very dark",
823 "Grayed",
824 },
825 .nStates = 4
826 }
827 },
828 .nConfigExtra = 3,
829 .setup = _setup,
830 .teardown = 0,
831 .gameLoaded = _gameLoaded,
832 .gameUnloaded = _gameUnloaded,
833 .prepareForFrame = 0,
834 .drawFrame = _drawFrame,
835 .drawScreenshot = _drawScreenshot,
836 .paused = _gameUnloaded,
837 .unpaused = _gameLoaded,
838 .incrementScreenMode = _incrementScreenMode,
839 .setFrameLimiter = _setFrameLimiter,
840 .pollGameInput = _pollGameInput
841 };
842
843 mGUIInit(&runner, "3ds");
844
845 _map3DSKey(&runner.params.keyMap, KEY_X, GUI_INPUT_CANCEL);
846 _map3DSKey(&runner.params.keyMap, KEY_Y, mGUI_INPUT_SCREEN_MODE);
847 _map3DSKey(&runner.params.keyMap, KEY_B, GUI_INPUT_BACK);
848 _map3DSKey(&runner.params.keyMap, KEY_A, GUI_INPUT_SELECT);
849 _map3DSKey(&runner.params.keyMap, KEY_UP, GUI_INPUT_UP);
850 _map3DSKey(&runner.params.keyMap, KEY_DOWN, GUI_INPUT_DOWN);
851 _map3DSKey(&runner.params.keyMap, KEY_LEFT, GUI_INPUT_LEFT);
852 _map3DSKey(&runner.params.keyMap, KEY_RIGHT, GUI_INPUT_RIGHT);
853 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_UP, mGUI_INPUT_INCREASE_BRIGHTNESS);
854 _map3DSKey(&runner.params.keyMap, KEY_CSTICK_DOWN, mGUI_INPUT_DECREASE_BRIGHTNESS);
855
856 mGUIRunloop(&runner);
857 mGUIDeinit(&runner);
858
859 _cleanup();
860 return 0;
861}