all repos — mgba @ 3a3062ee504659fbe0e5a506668083652c4d0377

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#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}