all repos — mgba @ 92c6b90b03f8c04b53312bf2273d86a9783ee073

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