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