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