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