all repos — mgba @ df619d4b56a2a3b83f718ae0a7b4440e913d9df0

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