all repos — mgba @ cd0a352a33d1613196ad5c3fa7e2af055e0fbecc

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.postVideoFrame = 0;
561	stream.postAudioFrame = 0;
562	stream.postAudioBuffer = _postAudioBuffer;
563
564	if (!allocateRomBuffer()) {
565		return 1;
566	}
567
568	aptHook(&cookie, _aptHook, 0);
569
570	ptmuInit();
571	hasSound = NO_SOUND;
572	if (!ndspInit()) {
573		hasSound = DSP_SUPPORTED;
574		ndspSetOutputMode(NDSP_OUTPUT_STEREO);
575		ndspSetOutputCount(1);
576		ndspChnReset(0);
577		ndspChnSetFormat(0, NDSP_FORMAT_STEREO_PCM16);
578		ndspChnSetInterp(0, NDSP_INTERP_NONE);
579		ndspChnSetRate(0, 0x8000);
580		ndspChnWaveBufClear(0);
581		audioLeft = linearMemAlign(AUDIO_SAMPLES * DSP_BUFFERS * 2 * sizeof(int16_t), 0x80);
582		memset(dspBuffer, 0, sizeof(dspBuffer));
583		int i;
584		for (i = 0; i < DSP_BUFFERS; ++i) {
585			dspBuffer[i].data_pcm16 = &audioLeft[AUDIO_SAMPLES * i * 2];
586			dspBuffer[i].nsamples = AUDIO_SAMPLES;
587		}
588	}
589
590	if (hasSound == NO_SOUND && !csndInit()) {
591		hasSound = CSND_SUPPORTED;
592		audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
593		audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80);
594	}
595
596	gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
597
598	if (ctrInitGpu() < 0) {
599		gbaOutputTexture.data = 0;
600		_cleanup();
601		return 1;
602	}
603
604	ctrTexture_Init(&gbaOutputTexture);
605	gbaOutputTexture.format = GPU_RGB565;
606	gbaOutputTexture.filter = GPU_LINEAR;
607	gbaOutputTexture.width = 256;
608	gbaOutputTexture.height = 256;
609	gbaOutputTexture.data = vramAlloc(256 * 256 * 2);
610	void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2;
611
612	if (!gbaOutputTexture.data) {
613		_cleanup();
614		return 1;
615	}
616
617	// Zero texture data to make sure no garbage around the border interferes with filtering
618	GX_MemoryFill(
619			gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER,
620			NULL, 0, NULL, 0);
621	gspWaitForPSC0();
622
623	sdmcArchive = (FS_Archive) {
624		ARCHIVE_SDMC,
625		(FS_Path) { PATH_EMPTY, 1, "" },
626		0
627	};
628	FSUSER_OpenArchive(&sdmcArchive);
629
630	struct GUIFont* font = GUIFontCreate();
631
632	if (!font) {
633		_cleanup();
634		return 1;
635	}
636
637	struct mGUIRunner runner = {
638		.params = {
639			320, 240,
640			font, "/",
641			_drawStart, _drawEnd,
642			_pollInput, _pollCursor,
643			_batteryState,
644			_guiPrepare, _guiFinish,
645
646			GUI_PARAMS_TRAIL
647		},
648		.keySources = (struct GUIInputKeys[]) {
649			{
650				.name = "3DS Input",
651				.id = _3DS_INPUT,
652				.keyNames = (const char*[]) {
653					"A",
654					"B",
655					"Select",
656					"Start",
657					"D-Pad Right",
658					"D-Pad Left",
659					"D-Pad Up",
660					"D-Pad Down",
661					"R",
662					"L",
663					"X",
664					"Y",
665					0,
666					0,
667					"ZL",
668					"ZR",
669					0,
670					0,
671					0,
672					0,
673					0,
674					0,
675					0,
676					0,
677					"C-Stick Right",
678					"C-Stick Left",
679					"C-Stick Up",
680					"C-Stick Down",
681				},
682				.nKeys = 28
683			},
684			{ .id = 0 }
685		},
686		.configExtra = (struct GUIMenuItem[]) {
687			{
688				.title = "Screen mode",
689				.data = "screenMode",
690				.submenu = 0,
691				.state = SM_PA_TOP,
692				.validStates = (const char*[]) {
693					"Pixel-Accurate/Bottom",
694					"Aspect-Ratio Fit/Bottom",
695					"Stretched/Bottom",
696					"Pixel-Accurate/Top",
697					"Aspect-Ratio Fit/Top",
698					"Stretched/Top",
699				},
700				.nStates = 6
701			}
702		},
703		.nConfigExtra = 1,
704		.setup = _setup,
705		.teardown = 0,
706		.gameLoaded = _gameLoaded,
707		.gameUnloaded = _gameUnloaded,
708		.prepareForFrame = 0,
709		.drawFrame = _drawFrame,
710		.drawScreenshot = _drawScreenshot,
711		.paused = _gameUnloaded,
712		.unpaused = _gameLoaded,
713		.incrementScreenMode = _incrementScreenMode,
714		.pollGameInput = _pollGameInput
715	};
716
717	mGUIInit(&runner, "3ds");
718	mGUIRunloop(&runner);
719	mGUIDeinit(&runner);
720
721	_cleanup();
722	return 0;
723}