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