all repos — mgba @ c0f0d54a52e4fef677f986e3a62bcabf454b3ca2

mGBA Game Boy Advance Emulator

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