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