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