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