all repos — mgba @ 5bd0bacb8bbf7eac890fb9570fcbb8ae1c28407d

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