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