all repos — mgba @ 8a68de198cd39e351ea84e040ecf5882b6234c03

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;
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, bool faded) {
413	UNUSED(runner);
414
415	C3D_Tex* tex = &outputTexture;
416
417	u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100);
418
419	// Convert image from RGBX8 to BGR565
420	for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
421		for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
422			// 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
423			u32 p = *pixels++;
424			newPixels[y * 256 + x] =
425				(p << 24 >> (24 + 3) << 11) | // R
426				(p << 16 >> (24 + 2) <<  5) | // G
427				(p <<  8 >> (24 + 3) <<  0);  // B
428		}
429		memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
430	}
431
432	GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
433	C3D_SafeDisplayTransfer(
434			(u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
435			tex->data, GX_BUFFER_DIM(256, 256),
436			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
437				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
438				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
439	gspWaitForPPF();
440	linearFree(newPixels);
441
442	_drawTex(runner->core, faded);
443}
444
445static uint16_t _pollGameInput(struct mGUIRunner* runner) {
446	UNUSED(runner);
447
448	hidScanInput();
449	uint32_t activeKeys = hidKeysHeld();
450	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
451	keys |= (activeKeys >> 24) & 0xF0;
452	return keys;
453}
454
455static void _incrementScreenMode(struct mGUIRunner* runner) {
456	UNUSED(runner);
457	screenMode = (screenMode + 1) % SM_MAX;
458	mCoreConfigSetUIntValue(&runner->core->config, "screenMode", screenMode);
459
460	C3D_RenderBufClear(&bottomScreen);
461	C3D_RenderBufClear(&topScreen);
462}
463
464static uint32_t _pollInput(void) {
465	hidScanInput();
466	uint32_t keys = 0;
467	int activeKeys = hidKeysHeld();
468	if (activeKeys & KEY_X) {
469		keys |= 1 << GUI_INPUT_CANCEL;
470	}
471	if (activeKeys & KEY_Y) {
472		keys |= 1 << mGUI_INPUT_SCREEN_MODE;
473	}
474	if (activeKeys & KEY_B) {
475		keys |= 1 << GUI_INPUT_BACK;
476	}
477	if (activeKeys & KEY_A) {
478		keys |= 1 << GUI_INPUT_SELECT;
479	}
480	if (activeKeys & KEY_LEFT) {
481		keys |= 1 << GUI_INPUT_LEFT;
482	}
483	if (activeKeys & KEY_RIGHT) {
484		keys |= 1 << GUI_INPUT_RIGHT;
485	}
486	if (activeKeys & KEY_UP) {
487		keys |= 1 << GUI_INPUT_UP;
488	}
489	if (activeKeys & KEY_DOWN) {
490		keys |= 1 << GUI_INPUT_DOWN;
491	}
492	if (activeKeys & KEY_CSTICK_UP) {
493		keys |= 1 << mGUI_INPUT_INCREASE_BRIGHTNESS;
494	}
495	if (activeKeys & KEY_CSTICK_DOWN) {
496		keys |= 1 << mGUI_INPUT_DECREASE_BRIGHTNESS;
497	}
498	return keys;
499}
500
501static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
502	hidScanInput();
503	if (!(hidKeysHeld() & KEY_TOUCH)) {
504		return GUI_CURSOR_NOT_PRESENT;
505	}
506	touchPosition pos;
507	hidTouchRead(&pos);
508	*x = pos.px;
509	*y = pos.py;
510	return GUI_CURSOR_DOWN;
511}
512
513static void _sampleRotation(struct mRotationSource* source) {
514	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
515	// Work around ctrulib getting the entries wrong
516	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
517	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
518}
519
520static int32_t _readTiltX(struct mRotationSource* source) {
521	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
522	return rotation->accel.x << 18L;
523}
524
525static int32_t _readTiltY(struct mRotationSource* source) {
526	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
527	return rotation->accel.y << 18L;
528}
529
530static int32_t _readGyroZ(struct mRotationSource* source) {
531	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
532	return rotation->gyro.y << 18L; // Yes, y
533}
534
535static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
536	UNUSED(stream);
537	if (hasSound == CSND_SUPPORTED) {
538		blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
539		blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
540		GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
541		GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
542		audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
543		if (audioPos == AUDIO_SAMPLES * 3) {
544			u8 playing = 0;
545			csndIsPlaying(0x8, &playing);
546			if (!playing) {
547				CSND_SetPlayState(0x8, 1);
548				CSND_SetPlayState(0x9, 1);
549				csndExecCmds(false);
550			}
551		}
552	} else if (hasSound == DSP_SUPPORTED) {
553		int startId = bufferId;
554		while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
555			bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
556			if (bufferId == startId) {
557				blip_clear(left);
558				blip_clear(right);
559				return;
560			}
561		}
562		void* tmpBuf = dspBuffer[bufferId].data_pcm16;
563		memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
564		dspBuffer[bufferId].data_pcm16 = tmpBuf;
565		dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
566		blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
567		blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
568		DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
569		ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
570	}
571}
572
573int main() {
574	rotation.d.sample = _sampleRotation;
575	rotation.d.readTiltX = _readTiltX;
576	rotation.d.readTiltY = _readTiltY;
577	rotation.d.readGyroZ = _readGyroZ;
578
579	stream.videoDimensionsChanged = 0;
580	stream.postVideoFrame = 0;
581	stream.postAudioFrame = 0;
582	stream.postAudioBuffer = _postAudioBuffer;
583
584	if (!allocateRomBuffer()) {
585		return 1;
586	}
587
588	aptHook(&cookie, _aptHook, 0);
589
590	ptmuInit();
591	hasSound = NO_SOUND;
592	if (!ndspInit()) {
593		hasSound = DSP_SUPPORTED;
594		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
595		ndspSetOutputCount(1);
596		ndspChnReset(0);
597		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
598		ndspChnSetInterp(0, NDSP_INTERP_NONE);
599		ndspChnSetRate(0, 0x8000);
600		ndspChnWaveBufClear(0);
601		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
602		memset(dspBuffer, 0, sizeof(dspBuffer));
603		int i;
604		for (i = 0; i < DSP_BUFFERS; ++i) {
605			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
606			dspBuffer[i].nsamples = AUDIO_SAMPLES;
607		}
608	}
609
610	if (hasSound == NO_SOUND && !csndInit()) {
611		hasSound = CSND_SUPPORTED;
612		audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
613		audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
614	}
615
616	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true);
617
618	if (!_initGpu()) {
619		outputTexture.data = 0;
620		_cleanup();
621		return 1;
622	}
623
624	if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) {
625		_cleanup();
626		return 1;
627	}
628	C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
629	C3D_TexSetFilter(&outputTexture, GPU_LINEAR, GPU_LINEAR);
630	void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2;
631
632	// Zero texture data to make sure no garbage around the border interferes with filtering
633	GX_MemoryFill(
634			outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
635			NULL, 0, NULL, 0);
636	gspWaitForPSC0();
637
638	FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""));
639
640	struct GUIFont* font = GUIFontCreate();
641
642	if (!font) {
643		_cleanup();
644		return 1;
645	}
646
647	struct mGUIRunner runner = {
648		.params = {
649			320, 240,
650			font, "/",
651			_drawStart, _drawEnd,
652			_pollInput, _pollCursor,
653			_batteryState,
654			_guiPrepare, _guiFinish,
655
656			GUI_PARAMS_TRAIL
657		},
658		.keySources = (struct GUIInputKeys[]) {
659			{
660				.name = "3DS Input",
661				.id = _3DS_INPUT,
662				.keyNames = (const char*[]) {
663					"A",
664					"B",
665					"Select",
666					"Start",
667					"D-Pad Right",
668					"D-Pad Left",
669					"D-Pad Up",
670					"D-Pad Down",
671					"R",
672					"L",
673					"X",
674					"Y",
675					0,
676					0,
677					"ZL",
678					"ZR",
679					0,
680					0,
681					0,
682					0,
683					0,
684					0,
685					0,
686					0,
687					"C-Stick Right",
688					"C-Stick Left",
689					"C-Stick Up",
690					"C-Stick Down",
691				},
692				.nKeys = 28
693			},
694			{ .id = 0 }
695		},
696		.configExtra = (struct GUIMenuItem[]) {
697			{
698				.title = "Screen mode",
699				.data = "screenMode",
700				.submenu = 0,
701				.state = SM_PA_TOP,
702				.validStates = (const char*[]) {
703					"Pixel-Accurate/Bottom",
704					"Aspect-Ratio Fit/Bottom",
705					"Stretched/Bottom",
706					"Pixel-Accurate/Top",
707					"Aspect-Ratio Fit/Top",
708					"Stretched/Top",
709				},
710				.nStates = 6
711			}
712		},
713		.nConfigExtra = 1,
714		.setup = _setup,
715		.teardown = 0,
716		.gameLoaded = _gameLoaded,
717		.gameUnloaded = _gameUnloaded,
718		.prepareForFrame = 0,
719		.drawFrame = _drawFrame,
720		.drawScreenshot = _drawScreenshot,
721		.paused = _gameUnloaded,
722		.unpaused = _gameLoaded,
723		.incrementScreenMode = _incrementScreenMode,
724		.pollGameInput = _pollGameInput
725	};
726
727	mGUIInit(&runner, "3ds");
728	mGUIRunloop(&runner);
729	mGUIDeinit(&runner);
730
731	_cleanup();
732	return 0;
733}