all repos — mgba @ 690346fd4edd32c05c97d916e25fc7421ae864c8

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