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