all repos — mgba @ 4edd7286f39fe940a890394626567211e072badb

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