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
38#define _3DS_INPUT 0x3344534B
39
40#define AUDIO_SAMPLES 384
41#define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 16)
42#define DSP_BUFFERS 4
43
44FS_Archive sdmcArchive;
45
46static struct GBA3DSRotationSource {
47 struct mRotationSource d;
48 accelVector accel;
49 angularRate gyro;
50} rotation;
51
52static enum {
53 NO_SOUND,
54 DSP_SUPPORTED,
55 CSND_SUPPORTED
56} hasSound;
57
58// TODO: Move into context
59static void* outputBuffer;
60static struct mAVStream stream;
61static int16_t* audioLeft = 0;
62static int16_t* audioRight = 0;
63static size_t audioPos = 0;
64static struct ctrTexture gbaOutputTexture;
65static int guiDrawn;
66static int screenCleanup;
67static ndspWaveBuf dspBuffer[DSP_BUFFERS];
68static int bufferId = 0;
69
70static aptHookCookie cookie;
71
72enum {
73 GUI_ACTIVE = 1,
74 GUI_THIS_FRAME = 2,
75};
76
77enum {
78 SCREEN_CLEANUP_TOP_1 = 1,
79 SCREEN_CLEANUP_TOP_2 = 2,
80 SCREEN_CLEANUP_TOP = SCREEN_CLEANUP_TOP_1 | SCREEN_CLEANUP_TOP_2,
81 SCREEN_CLEANUP_BOTTOM_1 = 4,
82 SCREEN_CLEANUP_BOTTOM_2 = 8,
83 SCREEN_CLEANUP_BOTTOM = SCREEN_CLEANUP_BOTTOM_1 | SCREEN_CLEANUP_BOTTOM_2,
84};
85
86extern bool allocateRomBuffer(void);
87
88static void _cleanup(void) {
89 if (outputBuffer) {
90 linearFree(outputBuffer);
91 }
92
93 if (gbaOutputTexture.data) {
94 ctrDeinitGpu();
95 vramFree(gbaOutputTexture.data);
96 }
97
98 gfxExit();
99
100 if (hasSound != NO_SOUND) {
101 linearFree(audioLeft);
102 }
103
104 if (hasSound == CSND_SUPPORTED) {
105 linearFree(audioRight);
106 csndExit();
107 }
108
109 if (hasSound == DSP_SUPPORTED) {
110 ndspExit();
111 }
112
113 csndExit();
114 ptmuExit();
115}
116
117static void _aptHook(APT_HookType hook, void* user) {
118 UNUSED(user);
119 switch (hook) {
120 case APTHOOK_ONSUSPEND:
121 case APTHOOK_ONSLEEP:
122 if (hasSound == CSND_SUPPORTED) {
123 CSND_SetPlayState(8, 0);
124 CSND_SetPlayState(9, 0);
125 csndExecCmds(false);
126 }
127 break;
128 case APTHOOK_ONEXIT:
129 if (hasSound == CSND_SUPPORTED) {
130 CSND_SetPlayState(8, 0);
131 CSND_SetPlayState(9, 0);
132 csndExecCmds(false);
133 }
134 _cleanup();
135 exit(0);
136 break;
137 default:
138 break;
139 }
140}
141
142static void _map3DSKey(struct mInputMap* map, int ctrKey, enum GBAKey key) {
143 mInputBindKey(map, _3DS_INPUT, __builtin_ctz(ctrKey), key);
144}
145
146static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, void* right, u32 size) {
147 u32 pleft = 0, pright = 0;
148
149 int loopMode = (flags >> 10) & 3;
150 if (!loopMode) {
151 flags |= SOUND_ONE_SHOT;
152 }
153
154 pleft = osConvertVirtToPhys(left);
155 pright = osConvertVirtToPhys(right);
156
157 u32 timer = CSND_TIMER(sampleRate);
158 if (timer < 0x0042) {
159 timer = 0x0042;
160 }
161 else if (timer > 0xFFFF) {
162 timer = 0xFFFF;
163 }
164 flags &= ~0xFFFF001F;
165 flags |= SOUND_ENABLE | (timer << 16);
166
167 u32 volumes = CSND_VOL(vol, -1.0);
168 CSND_SetChnRegs(flags | SOUND_CHANNEL(8), pleft, pleft, size, volumes, volumes);
169 volumes = CSND_VOL(vol, 1.0);
170 CSND_SetChnRegs(flags | SOUND_CHANNEL(9), pright, pright, size, volumes, volumes);
171}
172
173static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right);
174
175static void _drawStart(void) {
176 ctrGpuBeginDrawing();
177 if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) {
178 ctrGpuBeginFrame(GFX_BOTTOM);
179 ctrSetViewportSize(320, 240);
180 } else {
181 ctrGpuBeginFrame(GFX_TOP);
182 ctrSetViewportSize(400, 240);
183 }
184 guiDrawn &= ~GUI_THIS_FRAME;
185}
186
187static void _drawEnd(void) {
188 int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
189 u16 width = 0, height = 0;
190
191 void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width);
192 ctrGpuEndFrame(screen, outputFramebuffer, width, height);
193
194 if (screen != GFX_BOTTOM) {
195 if (guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE)) {
196 void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width);
197 ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height);
198 } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM) {
199 ctrGpuBeginFrame(GFX_BOTTOM);
200 if (screenCleanup & SCREEN_CLEANUP_BOTTOM_1) {
201 screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_1;
202 } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM_2) {
203 screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_2;
204 }
205 void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width);
206 ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height);
207 }
208 }
209
210 if ((screenCleanup & SCREEN_CLEANUP_TOP) && screen != GFX_TOP) {
211 ctrGpuBeginFrame(GFX_TOP);
212 if (screenCleanup & SCREEN_CLEANUP_TOP_1) {
213 screenCleanup &= ~SCREEN_CLEANUP_TOP_1;
214 } else if (screenCleanup & SCREEN_CLEANUP_TOP_2) {
215 screenCleanup &= ~SCREEN_CLEANUP_TOP_2;
216 }
217 void* outputFramebuffer = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &height, &width);
218 ctrGpuEndFrame(GFX_TOP, outputFramebuffer, width, height);
219 }
220
221 ctrGpuEndDrawing();
222}
223
224static int _batteryState(void) {
225 u8 charge;
226 u8 adapter;
227 PTMU_GetBatteryLevel(&charge);
228 PTMU_GetBatteryChargeState(&adapter);
229 int state = 0;
230 if (adapter) {
231 state |= BATTERY_CHARGING;
232 }
233 if (charge > 0) {
234 --charge;
235 }
236 return state | charge;
237}
238
239static void _guiPrepare(void) {
240 guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME;
241 int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
242 if (screen == GFX_BOTTOM) {
243 return;
244 }
245
246 ctrFlushBatch();
247 ctrGpuBeginFrame(GFX_BOTTOM);
248 ctrSetViewportSize(320, 240);
249}
250
251static void _guiFinish(void) {
252 guiDrawn &= ~GUI_ACTIVE;
253 screenCleanup |= SCREEN_CLEANUP_BOTTOM;
254}
255
256static void _setup(struct mGUIRunner* runner) {
257 runner->core->setRotation(runner->core, &rotation.d);
258 if (hasSound != NO_SOUND) {
259 runner->core->setAVStream(runner->core, &stream);
260 }
261
262 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
263 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
264 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
265 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
266 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
267 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
268 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
269 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
270 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
271 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
272
273 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
274 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
275
276 unsigned mode;
277 if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode < SM_MAX) {
278 screenMode = mode;
279 }
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->core->config, "screenMode", &mode) && mode != screenMode) {
324 screenMode = mode;
325 screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP;
326 }
327}
328
329static void _gameUnloaded(struct mGUIRunner* runner) {
330 if (hasSound == CSND_SUPPORTED) {
331 CSND_SetPlayState(8, 0);
332 CSND_SetPlayState(9, 0);
333 csndExecCmds(false);
334 }
335 osSetSpeedupEnable(false);
336
337 switch (runner->core->platform(runner->core)) {
338#ifdef M_CORE_GBA
339 case PLATFORM_GBA:
340 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
341 HIDUSER_DisableAccelerometer();
342 }
343 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
344 HIDUSER_DisableGyroscope();
345 }
346 break;
347#endif
348#ifdef M_CORE_GB
349 case PLATFORM_GB:
350 if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
351 HIDUSER_DisableAccelerometer();
352 }
353 break;
354#endif
355 default:
356 break;
357 }
358}
359
360static void _drawTex(struct mCore* core, bool faded) {
361 u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF;
362
363 int screen_w = screenMode < SM_PA_TOP ? 320 : 400;
364 int screen_h = 240;
365
366 unsigned corew, coreh;
367 core->desiredVideoDimensions(core, &corew, &coreh);
368
369 int w = corew;
370 int h = coreh;
371 // Get greatest common divisor
372 while (w != 0) {
373 int temp = h % w;
374 h = w;
375 w = temp;
376 }
377 int gcd = h;
378 int aspectw = corew / gcd;
379 int aspecth = coreh / gcd;
380
381 switch (screenMode) {
382 case SM_PA_TOP:
383 case SM_PA_BOTTOM:
384 default:
385 w = corew;
386 h = coreh;
387 break;
388 case SM_AF_TOP:
389 case SM_AF_BOTTOM:
390 w = screen_w / aspectw;
391 h = screen_h / aspecth;
392 if (w * aspecth > screen_h) {
393 w = aspectw * h;
394 h = aspecth * h;
395 } else {
396 h = aspecth * w;
397 w = aspectw * w;
398 }
399 break;
400 case SM_SF_TOP:
401 case SM_SF_BOTTOM:
402 w = screen_w;
403 h = screen_h;
404 break;
405 }
406
407 int x = (screen_w - w) / 2;
408 int y = (screen_h - h) / 2;
409
410 ctrAddRectScaled(color, x, y, w, h, 0, 0, corew, coreh);
411}
412
413static void _drawFrame(struct mGUIRunner* runner, bool faded) {
414 UNUSED(runner);
415
416 struct ctrTexture* tex = &gbaOutputTexture;
417
418 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
419 GX_DisplayTransfer(
420 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
421 tex->data, GX_BUFFER_DIM(256, 256),
422 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
423 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
424 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
425
426 if (hasSound == NO_SOUND) {
427 blip_clear(runner->core->getAudioChannel(runner->core, 0));
428 blip_clear(runner->core->getAudioChannel(runner->core, 1));
429 }
430
431 gspWaitForPPF();
432 ctrActivateTexture(tex);
433 _drawTex(runner->core, faded);
434}
435
436static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, bool faded) {
437 UNUSED(runner);
438
439 struct ctrTexture* tex = &gbaOutputTexture;
440
441 u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100);
442
443 // Convert image from RGBX8 to BGR565
444 for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
445 for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
446 // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
447 u32 p = *pixels++;
448 newPixels[y * 256 + x] =
449 (p << 24 >> (24 + 3) << 11) | // R
450 (p << 16 >> (24 + 2) << 5) | // G
451 (p << 8 >> (24 + 3) << 0); // B
452 }
453 memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
454 }
455
456 GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
457 GX_DisplayTransfer(
458 (u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
459 tex->data, GX_BUFFER_DIM(256, 256),
460 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
461 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
462 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
463 gspWaitForPPF();
464 linearFree(newPixels);
465
466 ctrActivateTexture(tex);
467 _drawTex(runner->core, faded);
468}
469
470static uint16_t _pollGameInput(struct mGUIRunner* runner) {
471 UNUSED(runner);
472
473 hidScanInput();
474 uint32_t activeKeys = hidKeysHeld();
475 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
476 keys |= (activeKeys >> 24) & 0xF0;
477 return keys;
478}
479
480static void _incrementScreenMode(struct mGUIRunner* runner) {
481 UNUSED(runner);
482 screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM;
483 screenMode = (screenMode + 1) % SM_MAX;
484 mCoreConfigSetUIntValue(&runner->core->config, "screenMode", screenMode);
485}
486
487static uint32_t _pollInput(void) {
488 hidScanInput();
489 uint32_t keys = 0;
490 int activeKeys = hidKeysHeld();
491 if (activeKeys & KEY_X) {
492 keys |= 1 << GUI_INPUT_CANCEL;
493 }
494 if (activeKeys & KEY_Y) {
495 keys |= 1 << mGUI_INPUT_SCREEN_MODE;
496 }
497 if (activeKeys & KEY_B) {
498 keys |= 1 << GUI_INPUT_BACK;
499 }
500 if (activeKeys & KEY_A) {
501 keys |= 1 << GUI_INPUT_SELECT;
502 }
503 if (activeKeys & KEY_LEFT) {
504 keys |= 1 << GUI_INPUT_LEFT;
505 }
506 if (activeKeys & KEY_RIGHT) {
507 keys |= 1 << GUI_INPUT_RIGHT;
508 }
509 if (activeKeys & KEY_UP) {
510 keys |= 1 << GUI_INPUT_UP;
511 }
512 if (activeKeys & KEY_DOWN) {
513 keys |= 1 << GUI_INPUT_DOWN;
514 }
515 if (activeKeys & KEY_CSTICK_UP) {
516 keys |= 1 << mGUI_INPUT_INCREASE_BRIGHTNESS;
517 }
518 if (activeKeys & KEY_CSTICK_DOWN) {
519 keys |= 1 << mGUI_INPUT_DECREASE_BRIGHTNESS;
520 }
521 return keys;
522}
523
524static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
525 hidScanInput();
526 if (!(hidKeysHeld() & KEY_TOUCH)) {
527 return GUI_CURSOR_NOT_PRESENT;
528 }
529 touchPosition pos;
530 hidTouchRead(&pos);
531 *x = pos.px;
532 *y = pos.py;
533 return GUI_CURSOR_DOWN;
534}
535
536static void _sampleRotation(struct mRotationSource* source) {
537 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
538 // Work around ctrulib getting the entries wrong
539 rotation->accel = *(accelVector*) &hidSharedMem[0x48];
540 rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
541}
542
543static int32_t _readTiltX(struct mRotationSource* source) {
544 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
545 return rotation->accel.x << 18L;
546}
547
548static int32_t _readTiltY(struct mRotationSource* source) {
549 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
550 return rotation->accel.y << 18L;
551}
552
553static int32_t _readGyroZ(struct mRotationSource* source) {
554 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
555 return rotation->gyro.y << 18L; // Yes, y
556}
557
558static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
559 UNUSED(stream);
560 if (hasSound == CSND_SUPPORTED) {
561 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
562 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
563 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
564 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
565 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
566 if (audioPos == AUDIO_SAMPLES * 3) {
567 u8 playing = 0;
568 csndIsPlaying(0x8, &playing);
569 if (!playing) {
570 CSND_SetPlayState(0x8, 1);
571 CSND_SetPlayState(0x9, 1);
572 csndExecCmds(false);
573 }
574 }
575 } else if (hasSound == DSP_SUPPORTED) {
576 int startId = bufferId;
577 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
578 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
579 if (bufferId == startId) {
580 blip_clear(left);
581 blip_clear(right);
582 return;
583 }
584 }
585 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
586 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
587 dspBuffer[bufferId].data_pcm16 = tmpBuf;
588 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
589 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
590 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
591 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
592 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
593 }
594}
595
596int main() {
597 rotation.d.sample = _sampleRotation;
598 rotation.d.readTiltX = _readTiltX;
599 rotation.d.readTiltY = _readTiltY;
600 rotation.d.readGyroZ = _readGyroZ;
601
602 stream.videoDimensionsChanged = 0;
603 stream.postVideoFrame = 0;
604 stream.postAudioFrame = 0;
605 stream.postAudioBuffer = _postAudioBuffer;
606
607 if (!allocateRomBuffer()) {
608 return 1;
609 }
610
611 aptHook(&cookie, _aptHook, 0);
612
613 ptmuInit();
614 hasSound = NO_SOUND;
615 if (!ndspInit()) {
616 hasSound = DSP_SUPPORTED;
617 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
618 ndspSetOutputCount(1);
619 ndspChnReset(0);
620 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
621 ndspChnSetInterp(0, NDSP_INTERP_NONE);
622 ndspChnSetRate(0, 0x8000);
623 ndspChnWaveBufClear(0);
624 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
625 memset(dspBuffer, 0, sizeof(dspBuffer));
626 int i;
627 for (i = 0; i < DSP_BUFFERS; ++i) {
628 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
629 dspBuffer[i].nsamples = AUDIO_SAMPLES;
630 }
631 }
632
633 if (hasSound == NO_SOUND && !csndInit()) {
634 hasSound = CSND_SUPPORTED;
635 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
636 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
637 }
638
639 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
640
641 if (ctrInitGpu() < 0) {
642 gbaOutputTexture.data = 0;
643 _cleanup();
644 return 1;
645 }
646
647 ctrTexture_Init(&gbaOutputTexture);
648 gbaOutputTexture.format = GPU_RGB565;
649 gbaOutputTexture.filter = GPU_LINEAR;
650 gbaOutputTexture.width = 256;
651 gbaOutputTexture.height = 256;
652 gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
653 void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
654
655 if (!gbaOutputTexture.data) {
656 _cleanup();
657 return 1;
658 }
659
660 // Zero texture data to make sure no garbage around the border interferes with filtering
661 GX_MemoryFill(
662 gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
663 NULL, 0, NULL, 0);
664 gspWaitForPSC0();
665
666 sdmcArchive = (FS_Archive) {
667 ARCHIVE_SDMC,
668 (FS_Path) { PATH_EMPTY, 1, "" },
669 0
670 };
671 FSUSER_OpenArchive(&sdmcArchive);
672
673 struct GUIFont* font = GUIFontCreate();
674
675 if (!font) {
676 _cleanup();
677 return 1;
678 }
679
680 struct mGUIRunner runner = {
681 .params = {
682 320, 240,
683 font, "/",
684 _drawStart, _drawEnd,
685 _pollInput, _pollCursor,
686 _batteryState,
687 _guiPrepare, _guiFinish,
688
689 GUI_PARAMS_TRAIL
690 },
691 .keySources = (struct GUIInputKeys[]) {
692 {
693 .name = "3DS Input",
694 .id = _3DS_INPUT,
695 .keyNames = (const char*[]) {
696 "A",
697 "B",
698 "Select",
699 "Start",
700 "D-Pad Right",
701 "D-Pad Left",
702 "D-Pad Up",
703 "D-Pad Down",
704 "R",
705 "L",
706 "X",
707 "Y",
708 0,
709 0,
710 "ZL",
711 "ZR",
712 0,
713 0,
714 0,
715 0,
716 0,
717 0,
718 0,
719 0,
720 "C-Stick Right",
721 "C-Stick Left",
722 "C-Stick Up",
723 "C-Stick Down",
724 },
725 .nKeys = 28
726 },
727 { .id = 0 }
728 },
729 .configExtra = (struct GUIMenuItem[]) {
730 {
731 .title = "Screen mode",
732 .data = "screenMode",
733 .submenu = 0,
734 .state = SM_PA_TOP,
735 .validStates = (const char*[]) {
736 "Pixel-Accurate/Bottom",
737 "Aspect-Ratio Fit/Bottom",
738 "Stretched/Bottom",
739 "Pixel-Accurate/Top",
740 "Aspect-Ratio Fit/Top",
741 "Stretched/Top",
742 },
743 .nStates = 6
744 }
745 },
746 .nConfigExtra = 1,
747 .setup = _setup,
748 .teardown = 0,
749 .gameLoaded = _gameLoaded,
750 .gameUnloaded = _gameUnloaded,
751 .prepareForFrame = 0,
752 .drawFrame = _drawFrame,
753 .drawScreenshot = _drawScreenshot,
754 .paused = _gameUnloaded,
755 .unpaused = _gameLoaded,
756 .incrementScreenMode = _incrementScreenMode,
757 .pollGameInput = _pollGameInput
758 };
759
760 mGUIInit(&runner, "3ds");
761 mGUIRunloop(&runner);
762 mGUIDeinit(&runner);
763
764 _cleanup();
765 return 0;
766}