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 ((struct GBA*) runner->core->board)->rotationSource = &rotation.d;
253 if (hasSound != NO_SOUND) {
254 ((struct GBA*) runner->core->board)->stream = &stream;
255 }
256
257 _map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
258 _map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
259 _map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
260 _map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
261 _map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
262 _map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
263 _map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
264 _map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
265 _map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
266 _map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
267
268 outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
269 runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
270
271 unsigned mode;
272 if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode < SM_MAX) {
273 screenMode = mode;
274 }
275
276 GBAAudioResizeBuffer(&((struct GBA*) runner->core->board)->audio, AUDIO_SAMPLES);
277}
278
279static void _gameLoaded(struct mGUIRunner* runner) {
280 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
281 HIDUSER_EnableAccelerometer();
282 }
283 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
284 HIDUSER_EnableGyroscope();
285 }
286 osSetSpeedupEnable(true);
287
288 double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
289 blip_set_rates(runner->core->getAudioChannel(runner->core, 0), GBA_ARM7TDMI_FREQUENCY, 32768 * ratio);
290 blip_set_rates(runner->core->getAudioChannel(runner->core, 1), GBA_ARM7TDMI_FREQUENCY, 32768 * ratio);
291 if (hasSound != NO_SOUND) {
292 audioPos = 0;
293 }
294 if (hasSound == CSND_SUPPORTED) {
295 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
296 memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
297 _csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
298 csndExecCmds(false);
299 } else if (hasSound == DSP_SUPPORTED) {
300 memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
301 }
302 unsigned mode;
303 if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode != screenMode) {
304 screenMode = mode;
305 screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP;
306 }
307}
308
309static void _gameUnloaded(struct mGUIRunner* runner) {
310 if (hasSound == CSND_SUPPORTED) {
311 CSND_SetPlayState(8, 0);
312 CSND_SetPlayState(9, 0);
313 csndExecCmds(false);
314 }
315 osSetSpeedupEnable(false);
316
317 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
318 HIDUSER_DisableAccelerometer();
319 }
320 if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
321 HIDUSER_DisableGyroscope();
322 }
323}
324
325static void _drawTex(bool faded) {
326 u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF;
327
328 int screen_w = screenMode < SM_PA_TOP ? 320 : 400;
329 int screen_h = 240;
330
331 int w, h;
332
333 switch (screenMode) {
334 case SM_PA_TOP:
335 case SM_PA_BOTTOM:
336 default:
337 w = VIDEO_HORIZONTAL_PIXELS;
338 h = VIDEO_VERTICAL_PIXELS;
339 break;
340 case SM_AF_TOP:
341 w = 360;
342 h = 240;
343 break;
344 case SM_AF_BOTTOM:
345 // Largest possible size with 3:2 aspect ratio and integer dimensions
346 w = 318;
347 h = 212;
348 break;
349 case SM_SF_TOP:
350 case SM_SF_BOTTOM:
351 w = screen_w;
352 h = screen_h;
353 break;
354 }
355
356 int x = (screen_w - w) / 2;
357 int y = (screen_h - h) / 2;
358
359 ctrAddRectScaled(color, x, y, w, h, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
360}
361
362static void _drawFrame(struct mGUIRunner* runner, bool faded) {
363 UNUSED(runner);
364
365 struct ctrTexture* tex = &gbaOutputTexture;
366
367 GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
368 GX_DisplayTransfer(
369 outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
370 tex->data, GX_BUFFER_DIM(256, 256),
371 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
372 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
373 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
374
375 if (hasSound == NO_SOUND) {
376 blip_clear(runner->core->getAudioChannel(runner->core, 0));
377 blip_clear(runner->core->getAudioChannel(runner->core, 1));
378 }
379
380 gspWaitForPPF();
381 ctrActivateTexture(tex);
382 _drawTex(faded);
383}
384
385static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, bool faded) {
386 UNUSED(runner);
387
388 struct ctrTexture* tex = &gbaOutputTexture;
389
390 u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100);
391
392 // Convert image from RGBX8 to BGR565
393 for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
394 for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
395 // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
396 u32 p = *pixels++;
397 newPixels[y * 256 + x] =
398 (p << 24 >> (24 + 3) << 11) | // R
399 (p << 16 >> (24 + 2) << 5) | // G
400 (p << 8 >> (24 + 3) << 0); // B
401 }
402 memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
403 }
404
405 GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
406 GX_DisplayTransfer(
407 (u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
408 tex->data, GX_BUFFER_DIM(256, 256),
409 GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
410 GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
411 GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
412 gspWaitForPPF();
413 linearFree(newPixels);
414
415 ctrActivateTexture(tex);
416 _drawTex(faded);
417}
418
419static uint16_t _pollGameInput(struct mGUIRunner* runner) {
420 UNUSED(runner);
421
422 hidScanInput();
423 uint32_t activeKeys = hidKeysHeld();
424 uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
425 keys |= (activeKeys >> 24) & 0xF0;
426 return keys;
427}
428
429static void _incrementScreenMode(struct mGUIRunner* runner) {
430 UNUSED(runner);
431 screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM;
432 screenMode = (screenMode + 1) % SM_MAX;
433 mCoreConfigSetUIntValue(&runner->core->config, "screenMode", screenMode);
434}
435
436static uint32_t _pollInput(void) {
437 hidScanInput();
438 uint32_t keys = 0;
439 int activeKeys = hidKeysHeld();
440 if (activeKeys & KEY_X) {
441 keys |= 1 << GUI_INPUT_CANCEL;
442 }
443 if (activeKeys & KEY_Y) {
444 keys |= 1 << mGUI_INPUT_SCREEN_MODE;
445 }
446 if (activeKeys & KEY_B) {
447 keys |= 1 << GUI_INPUT_BACK;
448 }
449 if (activeKeys & KEY_A) {
450 keys |= 1 << GUI_INPUT_SELECT;
451 }
452 if (activeKeys & KEY_LEFT) {
453 keys |= 1 << GUI_INPUT_LEFT;
454 }
455 if (activeKeys & KEY_RIGHT) {
456 keys |= 1 << GUI_INPUT_RIGHT;
457 }
458 if (activeKeys & KEY_UP) {
459 keys |= 1 << GUI_INPUT_UP;
460 }
461 if (activeKeys & KEY_DOWN) {
462 keys |= 1 << GUI_INPUT_DOWN;
463 }
464 if (activeKeys & KEY_CSTICK_UP) {
465 keys |= 1 << mGUI_INPUT_INCREASE_BRIGHTNESS;
466 }
467 if (activeKeys & KEY_CSTICK_DOWN) {
468 keys |= 1 << mGUI_INPUT_DECREASE_BRIGHTNESS;
469 }
470 return keys;
471}
472
473static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
474 hidScanInput();
475 if (!(hidKeysHeld() & KEY_TOUCH)) {
476 return GUI_CURSOR_NOT_PRESENT;
477 }
478 touchPosition pos;
479 hidTouchRead(&pos);
480 *x = pos.px;
481 *y = pos.py;
482 return GUI_CURSOR_DOWN;
483}
484
485static void _sampleRotation(struct mRotationSource* source) {
486 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
487 // Work around ctrulib getting the entries wrong
488 rotation->accel = *(accelVector*)& hidSharedMem[0x48];
489 rotation->gyro = *(angularRate*)& hidSharedMem[0x5C];
490}
491
492static int32_t _readTiltX(struct mRotationSource* source) {
493 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
494 return rotation->accel.x << 18L;
495}
496
497static int32_t _readTiltY(struct mRotationSource* source) {
498 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
499 return rotation->accel.y << 18L;
500}
501
502static int32_t _readGyroZ(struct mRotationSource* source) {
503 struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
504 return rotation->gyro.y << 18L; // Yes, y
505}
506
507static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
508 UNUSED(stream);
509 if (hasSound == CSND_SUPPORTED) {
510 blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
511 blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
512 GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
513 GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
514 audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
515 if (audioPos == AUDIO_SAMPLES * 3) {
516 u8 playing = 0;
517 csndIsPlaying(0x8, &playing);
518 if (!playing) {
519 CSND_SetPlayState(0x8, 1);
520 CSND_SetPlayState(0x9, 1);
521 csndExecCmds(false);
522 }
523 }
524 } else if (hasSound == DSP_SUPPORTED) {
525 int startId = bufferId;
526 while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
527 bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
528 if (bufferId == startId) {
529 blip_clear(left);
530 blip_clear(right);
531 return;
532 }
533 }
534 void* tmpBuf = dspBuffer[bufferId].data_pcm16;
535 memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
536 dspBuffer[bufferId].data_pcm16 = tmpBuf;
537 dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
538 blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
539 blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
540 DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
541 ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
542 }
543}
544
545int main() {
546 rotation.d.sample = _sampleRotation;
547 rotation.d.readTiltX = _readTiltX;
548 rotation.d.readTiltY = _readTiltY;
549 rotation.d.readGyroZ = _readGyroZ;
550
551 stream.postVideoFrame = 0;
552 stream.postAudioFrame = 0;
553 stream.postAudioBuffer = _postAudioBuffer;
554
555 if (!allocateRomBuffer()) {
556 return 1;
557 }
558
559 aptHook(&cookie, _aptHook, 0);
560
561 ptmuInit();
562 hasSound = NO_SOUND;
563 if (!ndspInit()) {
564 hasSound = DSP_SUPPORTED;
565 ndspSetOutputMode(NDSP_OUTPUT_STEREO);
566 ndspSetOutputCount(1);
567 ndspChnReset(0);
568 ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
569 ndspChnSetInterp(0, NDSP_INTERP_NONE);
570 ndspChnSetRate(0, 0x8000);
571 ndspChnWaveBufClear(0);
572 audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
573 memset(dspBuffer, 0, sizeof(dspBuffer));
574 int i;
575 for (i = 0; i < DSP_BUFFERS; ++i) {
576 dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
577 dspBuffer[i].nsamples = AUDIO_SAMPLES;
578 }
579 }
580
581 if (hasSound == NO_SOUND && !csndInit()) {
582 hasSound = CSND_SUPPORTED;
583 audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
584 audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
585 }
586
587 gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
588
589 if (ctrInitGpu() < 0) {
590 gbaOutputTexture.data = 0;
591 _cleanup();
592 return 1;
593 }
594
595 ctrTexture_Init(&gbaOutputTexture);
596 gbaOutputTexture.format = GPU_RGB565;
597 gbaOutputTexture.filter = GPU_LINEAR;
598 gbaOutputTexture.width = 256;
599 gbaOutputTexture.height = 256;
600 gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
601 void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
602
603 if (!gbaOutputTexture.data) {
604 _cleanup();
605 return 1;
606 }
607
608 // Zero texture data to make sure no garbage around the border interferes with filtering
609 GX_MemoryFill(
610 gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
611 NULL, 0, NULL, 0);
612 gspWaitForPSC0();
613
614 sdmcArchive = (FS_Archive) {
615 ARCHIVE_SDMC,
616 (FS_Path) { PATH_EMPTY, 1, "" },
617 0
618 };
619 FSUSER_OpenArchive(&sdmcArchive);
620
621 struct GUIFont* font = GUIFontCreate();
622
623 if (!font) {
624 _cleanup();
625 return 1;
626 }
627
628 struct mGUIRunner runner = {
629 .params = {
630 320, 240,
631 font, "/",
632 _drawStart, _drawEnd,
633 _pollInput, _pollCursor,
634 _batteryState,
635 _guiPrepare, _guiFinish,
636
637 GUI_PARAMS_TRAIL
638 },
639 .keySources = (struct GUIInputKeys[]) {
640 {
641 .name = "3DS Input",
642 .id = _3DS_INPUT,
643 .keyNames = (const char*[]) {
644 "A",
645 "B",
646 "Select",
647 "Start",
648 "D-Pad Right",
649 "D-Pad Left",
650 "D-Pad Up",
651 "D-Pad Down",
652 "R",
653 "L",
654 "X",
655 "Y",
656 0,
657 0,
658 "ZL",
659 "ZR",
660 0,
661 0,
662 0,
663 0,
664 0,
665 0,
666 0,
667 0,
668 "C-Stick Right",
669 "C-Stick Left",
670 "C-Stick Up",
671 "C-Stick Down",
672 },
673 .nKeys = 28
674 },
675 { .id = 0 }
676 },
677 .configExtra = (struct GUIMenuItem[]) {
678 {
679 .title = "Screen mode",
680 .data = "screenMode",
681 .submenu = 0,
682 .state = SM_PA_TOP,
683 .validStates = (const char*[]) {
684 "Pixel-Accurate/Bottom",
685 "Aspect-Ratio Fit/Bottom",
686 "Stretched/Bottom",
687 "Pixel-Accurate/Top",
688 "Aspect-Ratio Fit/Top",
689 "Stretched/Top",
690 },
691 .nStates = 6
692 }
693 },
694 .nConfigExtra = 1,
695 .setup = _setup,
696 .teardown = 0,
697 .gameLoaded = _gameLoaded,
698 .gameUnloaded = _gameUnloaded,
699 .prepareForFrame = 0,
700 .drawFrame = _drawFrame,
701 .drawScreenshot = _drawScreenshot,
702 .paused = _gameUnloaded,
703 .unpaused = _gameLoaded,
704 .incrementScreenMode = _incrementScreenMode,
705 .pollGameInput = _pollGameInput
706 };
707
708 mGUIInit(&runner, "3ds");
709 mGUIRunloop(&runner);
710 mGUIDeinit(&runner);
711
712 _cleanup();
713 return 0;
714}