all repos — mgba @ df8cddb289bece5bd4ed0db9355048ecd006d12c

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/gui/gui-runner.h"
 10#include "gba/input.h"
 11#include "gba/video.h"
 12#endif
 13#ifdef M_CORE_GB
 14#include "gb/gb.h"
 15#endif
 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 struct ctrTexture gbaOutputTexture;
 65static int guiDrawn;
 66static int screenCleanup;
 67static ndspWaveBuf dspBuffer[DSP_BUFFERS];
 68static int bufferId = 0;
 69
 70static aptHookCookie cookie;
 71
 72enum {
 73	GUI_ACTIVE = 1,
 74	GUI_THIS_FRAME = 2,
 75};
 76
 77enum {
 78	SCREEN_CLEANUP_TOP_1 = 1,
 79	SCREEN_CLEANUP_TOP_2 = 2,
 80	SCREEN_CLEANUP_TOP = SCREEN_CLEANUP_TOP_1 | SCREEN_CLEANUP_TOP_2,
 81	SCREEN_CLEANUP_BOTTOM_1 = 4,
 82	SCREEN_CLEANUP_BOTTOM_2 = 8,
 83	SCREEN_CLEANUP_BOTTOM = SCREEN_CLEANUP_BOTTOM_1 | SCREEN_CLEANUP_BOTTOM_2,
 84};
 85
 86extern bool allocateRomBuffer(void);
 87
 88static void _cleanup(void) {
 89	if (outputBuffer) {
 90		linearFree(outputBuffer);
 91	}
 92
 93	if (gbaOutputTexture.data) {
 94		ctrDeinitGpu();
 95		vramFree(gbaOutputTexture.data);
 96	}
 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	ctrGpuBeginDrawing();
177	if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) {
178		ctrGpuBeginFrame(GFX_BOTTOM);
179		ctrSetViewportSize(320, 240);
180	} else {
181		ctrGpuBeginFrame(GFX_TOP);
182		ctrSetViewportSize(400, 240);
183	}
184	guiDrawn &= ~GUI_THIS_FRAME;
185}
186
187static void _drawEnd(void) {
188	int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
189	u16 width = 0, height = 0;
190
191	void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width);
192	ctrGpuEndFrame(screen, outputFramebuffer, width, height);
193
194	if (screen != GFX_BOTTOM) {
195		if (guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE)) {
196			void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width);
197			ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height);
198		} else if (screenCleanup & SCREEN_CLEANUP_BOTTOM) {
199			ctrGpuBeginFrame(GFX_BOTTOM);
200			if (screenCleanup & SCREEN_CLEANUP_BOTTOM_1) {
201				screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_1;
202			} else if (screenCleanup & SCREEN_CLEANUP_BOTTOM_2) {
203				screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_2;
204			}
205			void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width);
206			ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height);
207		}
208	}
209
210	if ((screenCleanup & SCREEN_CLEANUP_TOP) && screen != GFX_TOP) {
211		ctrGpuBeginFrame(GFX_TOP);
212		if (screenCleanup & SCREEN_CLEANUP_TOP_1) {
213			screenCleanup &= ~SCREEN_CLEANUP_TOP_1;
214		} else if (screenCleanup & SCREEN_CLEANUP_TOP_2) {
215			screenCleanup &= ~SCREEN_CLEANUP_TOP_2;
216		}
217		void* outputFramebuffer = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &height, &width);
218		ctrGpuEndFrame(GFX_TOP, outputFramebuffer, width, height);
219	}
220
221	ctrGpuEndDrawing();
222}
223
224static int _batteryState(void) {
225	u8 charge;
226	u8 adapter;
227	PTMU_GetBatteryLevel(&charge);
228	PTMU_GetBatteryChargeState(&adapter);
229	int state = 0;
230	if (adapter) {
231		state |= BATTERY_CHARGING;
232	}
233	if (charge > 0) {
234		--charge;
235	}
236	return state | charge;
237}
238
239static void _guiPrepare(void) {
240	guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME;
241	int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP;
242	if (screen == GFX_BOTTOM) {
243		return;
244	}
245
246	ctrFlushBatch();
247	ctrGpuBeginFrame(GFX_BOTTOM);
248	ctrSetViewportSize(320, 240);
249}
250
251static void _guiFinish(void) {
252	guiDrawn &= ~GUI_ACTIVE;
253	screenCleanup |= SCREEN_CLEANUP_BOTTOM;
254}
255
256static void _setup(struct mGUIRunner* runner) {
257	runner->core->setRotation(runner->core, &rotation.d);
258	if (hasSound != NO_SOUND) {
259		runner->core->setAVStream(runner->core, &stream);
260	}
261
262	_map3DSKey(&runner->core->inputMap, KEY_A, GBA_KEY_A);
263	_map3DSKey(&runner->core->inputMap, KEY_B, GBA_KEY_B);
264	_map3DSKey(&runner->core->inputMap, KEY_START, GBA_KEY_START);
265	_map3DSKey(&runner->core->inputMap, KEY_SELECT, GBA_KEY_SELECT);
266	_map3DSKey(&runner->core->inputMap, KEY_UP, GBA_KEY_UP);
267	_map3DSKey(&runner->core->inputMap, KEY_DOWN, GBA_KEY_DOWN);
268	_map3DSKey(&runner->core->inputMap, KEY_LEFT, GBA_KEY_LEFT);
269	_map3DSKey(&runner->core->inputMap, KEY_RIGHT, GBA_KEY_RIGHT);
270	_map3DSKey(&runner->core->inputMap, KEY_L, GBA_KEY_L);
271	_map3DSKey(&runner->core->inputMap, KEY_R, GBA_KEY_R);
272
273	outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80);
274	runner->core->setVideoBuffer(runner->core, outputBuffer, 256);
275
276	unsigned mode;
277	if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode < SM_MAX) {
278		screenMode = mode;
279	}
280
281	runner->core->setAudioBufferSize(runner->core, AUDIO_SAMPLES);
282}
283
284static void _gameLoaded(struct mGUIRunner* runner) {
285	switch (runner->core->platform(runner->core)) {
286#ifdef M_CORE_GBA
287	case PLATFORM_GBA:
288		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
289			HIDUSER_EnableAccelerometer();
290		}
291		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
292			HIDUSER_EnableGyroscope();
293		}
294		break;
295#endif
296#ifdef M_CORE_GB
297	case PLATFORM_GB:
298		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
299			HIDUSER_EnableAccelerometer();
300		}
301		break;
302#endif
303	default:
304		break;
305	}
306	osSetSpeedupEnable(true);
307
308	double ratio = GBAAudioCalculateRatio(1, 59.8260982880808, 1);
309	blip_set_rates(runner->core->getAudioChannel(runner->core, 0), runner->core->frequency(runner->core), 32768 * ratio);
310	blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), 32768 * ratio);
311	if (hasSound != NO_SOUND) {
312		audioPos = 0;
313	}
314	if (hasSound == CSND_SUPPORTED) {
315		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
316		memset(audioRight, 0, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
317		_csndPlaySound(SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, audioLeft, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t));
318		csndExecCmds(false);
319	} else if (hasSound == DSP_SUPPORTED) {
320		memset(audioLeft, 0, AUDIO_SAMPLE_BUFFER * 2 * sizeof(int16_t));
321	}
322	unsigned mode;
323	if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode != screenMode) {
324		screenMode = mode;
325		screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP;
326	}
327}
328
329static void _gameUnloaded(struct mGUIRunner* runner) {
330	if (hasSound == CSND_SUPPORTED) {
331		CSND_SetPlayState(8, 0);
332		CSND_SetPlayState(9, 0);
333		csndExecCmds(false);
334	}
335	osSetSpeedupEnable(false);
336
337	switch (runner->core->platform(runner->core)) {
338#ifdef M_CORE_GBA
339	case PLATFORM_GBA:
340		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_TILT) {
341			HIDUSER_DisableAccelerometer();
342		}
343		if (((struct GBA*) runner->core->board)->memory.hw.devices & HW_GYRO) {
344			HIDUSER_DisableGyroscope();
345		}
346		break;
347#endif
348#ifdef M_CORE_GB
349	case PLATFORM_GB:
350		if (((struct GB*) runner->core->board)->memory.mbcType == GB_MBC7) {
351			HIDUSER_DisableAccelerometer();
352		}
353		break;
354#endif
355	default:
356		break;
357	}
358}
359
360static void _drawTex(struct mCore* core, bool faded) {
361	u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF;
362
363	int screen_w = screenMode < SM_PA_TOP ? 320 : 400;
364	int screen_h = 240;
365
366	unsigned corew, coreh;
367	core->desiredVideoDimensions(core, &corew, &coreh);
368
369	int w = corew;
370	int h = coreh;
371	// Get greatest common divisor
372	while (w != 0) {
373		int temp = h % w;
374		h = w;
375		w = temp;
376	}
377	int gcd = h;
378	int aspectw = corew / gcd;
379	int aspecth = coreh / gcd;
380
381	switch (screenMode) {
382	case SM_PA_TOP:
383	case SM_PA_BOTTOM:
384	default:
385		w = corew;
386		h = coreh;
387		break;
388	case SM_AF_TOP:
389	case SM_AF_BOTTOM:
390		w = screen_w / aspectw;
391		h = screen_h / aspecth;
392		if (w * aspecth > screen_h) {
393			w = aspectw * h;
394			h = aspecth * h;
395		} else {
396			h = aspecth * w;
397			w = aspectw * w;
398		}
399		break;
400	case SM_SF_TOP:
401	case SM_SF_BOTTOM:
402		w = screen_w;
403		h = screen_h;
404		break;
405	}
406
407	int x = (screen_w - w) / 2;
408	int y = (screen_h - h) / 2;
409
410	ctrAddRectScaled(color, x, y, w, h, 0, 0, corew, coreh);
411}
412
413static void _drawFrame(struct mGUIRunner* runner, bool faded) {
414	UNUSED(runner);
415
416	struct ctrTexture* tex = &gbaOutputTexture;
417
418	GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2);
419	GX_DisplayTransfer(
420			outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
421			tex->data, GX_BUFFER_DIM(256, 256),
422			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
423				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
424				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
425
426	if (hasSound == NO_SOUND) {
427		blip_clear(runner->core->getAudioChannel(runner->core, 0));
428		blip_clear(runner->core->getAudioChannel(runner->core, 1));
429	}
430
431	gspWaitForPPF();
432	ctrActivateTexture(tex);
433	_drawTex(runner->core, faded);
434}
435
436static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, bool faded) {
437	UNUSED(runner);
438
439	struct ctrTexture* tex = &gbaOutputTexture;
440
441	u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100);
442
443	// Convert image from RGBX8 to BGR565
444	for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
445		for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
446			// 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB
447			u32 p = *pixels++;
448			newPixels[y * 256 + x] =
449				(p << 24 >> (24 + 3) << 11) | // R
450				(p << 16 >> (24 + 2) <<  5) | // G
451				(p <<  8 >> (24 + 3) <<  0);  // B
452		}
453		memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32));
454	}
455
456	GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32));
457	GX_DisplayTransfer(
458			(u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS),
459			tex->data, GX_BUFFER_DIM(256, 256),
460			GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) |
461				GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) |
462				GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1));
463	gspWaitForPPF();
464	linearFree(newPixels);
465
466	ctrActivateTexture(tex);
467	_drawTex(runner->core, faded);
468}
469
470static uint16_t _pollGameInput(struct mGUIRunner* runner) {
471	UNUSED(runner);
472
473	hidScanInput();
474	uint32_t activeKeys = hidKeysHeld();
475	uint16_t keys = mInputMapKeyBits(&runner->core->inputMap, _3DS_INPUT, activeKeys, 0);
476	keys |= (activeKeys >> 24) & 0xF0;
477	return keys;
478}
479
480static void _incrementScreenMode(struct mGUIRunner* runner) {
481	UNUSED(runner);
482	screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM;
483	screenMode = (screenMode + 1) % SM_MAX;
484	mCoreConfigSetUIntValue(&runner->core->config, "screenMode", screenMode);
485}
486
487static uint32_t _pollInput(void) {
488	hidScanInput();
489	uint32_t keys = 0;
490	int activeKeys = hidKeysHeld();
491	if (activeKeys & KEY_X) {
492		keys |= 1 << GUI_INPUT_CANCEL;
493	}
494	if (activeKeys & KEY_Y) {
495		keys |= 1 << mGUI_INPUT_SCREEN_MODE;
496	}
497	if (activeKeys & KEY_B) {
498		keys |= 1 << GUI_INPUT_BACK;
499	}
500	if (activeKeys & KEY_A) {
501		keys |= 1 << GUI_INPUT_SELECT;
502	}
503	if (activeKeys & KEY_LEFT) {
504		keys |= 1 << GUI_INPUT_LEFT;
505	}
506	if (activeKeys & KEY_RIGHT) {
507		keys |= 1 << GUI_INPUT_RIGHT;
508	}
509	if (activeKeys & KEY_UP) {
510		keys |= 1 << GUI_INPUT_UP;
511	}
512	if (activeKeys & KEY_DOWN) {
513		keys |= 1 << GUI_INPUT_DOWN;
514	}
515	if (activeKeys & KEY_CSTICK_UP) {
516		keys |= 1 << mGUI_INPUT_INCREASE_BRIGHTNESS;
517	}
518	if (activeKeys & KEY_CSTICK_DOWN) {
519		keys |= 1 << mGUI_INPUT_DECREASE_BRIGHTNESS;
520	}
521	return keys;
522}
523
524static enum GUICursorState _pollCursor(unsigned* x, unsigned* y) {
525	hidScanInput();
526	if (!(hidKeysHeld() & KEY_TOUCH)) {
527		return GUI_CURSOR_NOT_PRESENT;
528	}
529	touchPosition pos;
530	hidTouchRead(&pos);
531	*x = pos.px;
532	*y = pos.py;
533	return GUI_CURSOR_DOWN;
534}
535
536static void _sampleRotation(struct mRotationSource* source) {
537	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
538	// Work around ctrulib getting the entries wrong
539	rotation->accel = *(accelVector*) &hidSharedMem[0x48];
540	rotation->gyro = *(angularRate*) &hidSharedMem[0x5C];
541}
542
543static int32_t _readTiltX(struct mRotationSource* source) {
544	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
545	return rotation->accel.x << 18L;
546}
547
548static int32_t _readTiltY(struct mRotationSource* source) {
549	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
550	return rotation->accel.y << 18L;
551}
552
553static int32_t _readGyroZ(struct mRotationSource* source) {
554	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
555	return rotation->gyro.y << 18L; // Yes, y
556}
557
558static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
559	UNUSED(stream);
560	if (hasSound == CSND_SUPPORTED) {
561		blip_read_samples(left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
562		blip_read_samples(right, &audioRight[audioPos], AUDIO_SAMPLES, false);
563		GSPGPU_FlushDataCache(&audioLeft[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
564		GSPGPU_FlushDataCache(&audioRight[audioPos], AUDIO_SAMPLES * sizeof(int16_t));
565		audioPos = (audioPos + AUDIO_SAMPLES) % AUDIO_SAMPLE_BUFFER;
566		if (audioPos == AUDIO_SAMPLES * 3) {
567			u8 playing = 0;
568			csndIsPlaying(0x8, &playing);
569			if (!playing) {
570				CSND_SetPlayState(0x8, 1);
571				CSND_SetPlayState(0x9, 1);
572				csndExecCmds(false);
573			}
574		}
575	} else if (hasSound == DSP_SUPPORTED) {
576		int startId = bufferId;
577		while (dspBuffer[bufferId].status == NDSP_WBUF_QUEUED || dspBuffer[bufferId].status == NDSP_WBUF_PLAYING) {
578			bufferId = (bufferId + 1) & (DSP_BUFFERS - 1);
579			if (bufferId == startId) {
580				blip_clear(left);
581				blip_clear(right);
582				return;
583			}
584		}
585		void* tmpBuf = dspBuffer[bufferId].data_pcm16;
586		memset(&dspBuffer[bufferId], 0, sizeof(dspBuffer[bufferId]));
587		dspBuffer[bufferId].data_pcm16 = tmpBuf;
588		dspBuffer[bufferId].nsamples = AUDIO_SAMPLES;
589		blip_read_samples(left, dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES, true);
590		blip_read_samples(right, dspBuffer[bufferId].data_pcm16 + 1, AUDIO_SAMPLES, true);
591		DSP_FlushDataCache(dspBuffer[bufferId].data_pcm16, AUDIO_SAMPLES * 2 * sizeof(int16_t));
592		ndspChnWaveBufAdd(0, &dspBuffer[bufferId]);
593	}
594}
595
596int main() {
597	rotation.d.sample = _sampleRotation;
598	rotation.d.readTiltX = _readTiltX;
599	rotation.d.readTiltY = _readTiltY;
600	rotation.d.readGyroZ = _readGyroZ;
601
602	stream.videoDimensionsChanged = 0;
603	stream.postVideoFrame = 0;
604	stream.postAudioFrame = 0;
605	stream.postAudioBuffer = _postAudioBuffer;
606
607	if (!allocateRomBuffer()) {
608		return 1;
609	}
610
611	aptHook(&cookie, _aptHook, 0);
612
613	ptmuInit();
614	hasSound = NO_SOUND;
615	if (!ndspInit()) {
616		hasSound = DSP_SUPPORTED;
617		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
618		ndspSetOutputCount(1);
619		ndspChnReset(0);
620		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
621		ndspChnSetInterp(0, NDSP_INTERP_NONE);
622		ndspChnSetRate(0, 0x8000);
623		ndspChnWaveBufClear(0);
624		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
625		memset(dspBuffer, 0, sizeof(dspBuffer));
626		int i;
627		for (i = 0; i < DSP_BUFFERS; ++i) {
628			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
629			dspBuffer[i].nsamples = AUDIO_SAMPLES;
630		}
631	}
632
633	if (hasSound == NO_SOUND && !csndInit()) {
634		hasSound = CSND_SUPPORTED;
635		audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
636		audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
637	}
638
639	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
640
641	if (ctrInitGpu() < 0) {
642		gbaOutputTexture.data = 0;
643		_cleanup();
644		return 1;
645	}
646
647	ctrTexture_Init(&gbaOutputTexture);
648	gbaOutputTexture.format = GPU_RGB565;
649	gbaOutputTexture.filter = GPU_LINEAR;
650	gbaOutputTexture.width = 256;
651	gbaOutputTexture.height = 256;
652	gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
653	void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
654
655	if (!gbaOutputTexture.data) {
656		_cleanup();
657		return 1;
658	}
659
660	// Zero texture data to make sure no garbage around the border interferes with filtering
661	GX_MemoryFill(
662			gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
663			NULL, 0, NULL, 0);
664	gspWaitForPSC0();
665
666	sdmcArchive = (FS_Archive) {
667		ARCHIVE_SDMC,
668		(FS_Path) { PATH_EMPTY, 1, "" },
669		0
670	};
671	FSUSER_OpenArchive(&sdmcArchive);
672
673	struct GUIFont* font = GUIFontCreate();
674
675	if (!font) {
676		_cleanup();
677		return 1;
678	}
679
680	struct mGUIRunner runner = {
681		.params = {
682			320, 240,
683			font, "/",
684			_drawStart, _drawEnd,
685			_pollInput, _pollCursor,
686			_batteryState,
687			_guiPrepare, _guiFinish,
688
689			GUI_PARAMS_TRAIL
690		},
691		.keySources = (struct GUIInputKeys[]) {
692			{
693				.name = "3DS Input",
694				.id = _3DS_INPUT,
695				.keyNames = (const char*[]) {
696					"A",
697					"B",
698					"Select",
699					"Start",
700					"D-Pad Right",
701					"D-Pad Left",
702					"D-Pad Up",
703					"D-Pad Down",
704					"R",
705					"L",
706					"X",
707					"Y",
708					0,
709					0,
710					"ZL",
711					"ZR",
712					0,
713					0,
714					0,
715					0,
716					0,
717					0,
718					0,
719					0,
720					"C-Stick Right",
721					"C-Stick Left",
722					"C-Stick Up",
723					"C-Stick Down",
724				},
725				.nKeys = 28
726			},
727			{ .id = 0 }
728		},
729		.configExtra = (struct GUIMenuItem[]) {
730			{
731				.title = "Screen mode",
732				.data = "screenMode",
733				.submenu = 0,
734				.state = SM_PA_TOP,
735				.validStates = (const char*[]) {
736					"Pixel-Accurate/Bottom",
737					"Aspect-Ratio Fit/Bottom",
738					"Stretched/Bottom",
739					"Pixel-Accurate/Top",
740					"Aspect-Ratio Fit/Top",
741					"Stretched/Top",
742				},
743				.nStates = 6
744			}
745		},
746		.nConfigExtra = 1,
747		.setup = _setup,
748		.teardown = 0,
749		.gameLoaded = _gameLoaded,
750		.gameUnloaded = _gameUnloaded,
751		.prepareForFrame = 0,
752		.drawFrame = _drawFrame,
753		.drawScreenshot = _drawScreenshot,
754		.paused = _gameUnloaded,
755		.unpaused = _gameLoaded,
756		.incrementScreenMode = _incrementScreenMode,
757		.pollGameInput = _pollGameInput
758	};
759
760	mGUIInit(&runner, "3ds");
761	mGUIRunloop(&runner);
762	mGUIDeinit(&runner);
763
764	_cleanup();
765	return 0;
766}