all repos — mgba @ c0d7cfbf0b99e4cf6e28a0a2b9727e5fc2a453ab

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/memory.h"
 15
 16#include "3ds-vfs.h"
 17
 18#include <3ds.h>
 19#include <sf2d.h>
 20
 21#define AUDIO_SAMPLES 0x800
 22
 23FS_archive sdmcArchive;
 24
 25static struct GBA3DSRotationSource {
 26	struct GBARotationSource d;
 27	accelVector accel;
 28	angularRate gyro;
 29} rotation;
 30
 31static struct VFile* logFile;
 32static bool hasSound;
 33// TODO: Move into context
 34static struct GBAVideoSoftwareRenderer renderer;
 35static int16_t* audioLeft = 0;
 36static int16_t* audioRight = 0;
 37static sf2d_texture* tex;
 38static size_t audioPos = 0;
 39
 40extern bool allocateRomBuffer(void);
 41static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
 42
 43static void _drawStart(void) {
 44	sf2d_start_frame(GFX_BOTTOM, GFX_LEFT);
 45}
 46
 47static void _drawEnd(void) {
 48	sf2d_end_frame();
 49	sf2d_swapbuffers();
 50}
 51
 52static void _setup(struct GBAGUIRunner* runner) {
 53	struct GBAOptions opts = {
 54		.useBios = true,
 55		.logLevel = 0,
 56		.idleOptimization = IDLE_LOOP_DETECT
 57	};
 58	GBAConfigLoadDefaults(&runner->context.config, &opts);
 59	runner->context.gba->logHandler = GBA3DSLog;
 60	runner->context.gba->rotationSource = &rotation.d;
 61
 62	GBAVideoSoftwareRendererCreate(&renderer);
 63	renderer.outputBuffer = anonymousMemoryMap(256 * VIDEO_VERTICAL_PIXELS * 2);
 64	renderer.outputBufferStride = 256;
 65	runner->context.renderer = &renderer.d;
 66}
 67
 68static void _gameLoaded(struct GBAGUIRunner* runner) {
 69	if (runner->context.gba->memory.hw.devices & HW_TILT) {
 70		HIDUSER_EnableAccelerometer();
 71	}
 72	if (runner->context.gba->memory.hw.devices & HW_GYRO) {
 73		HIDUSER_EnableGyroscope();
 74	}
 75
 76#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
 77	blip_set_rates(runner->context.gba->audio.left,  GBA_ARM7TDMI_FREQUENCY, 0x8000);
 78	blip_set_rates(runner->context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 0x8000);
 79#endif
 80	if (hasSound) {
 81		memset(audioLeft, 0, AUDIO_SAMPLES * 2 * sizeof(int16_t));
 82		memset(audioRight, 0, AUDIO_SAMPLES * 2 * sizeof(int16_t));
 83	}
 84}
 85
 86static void _gameUnloaded(struct GBAGUIRunner* runner) {
 87	CSND_SetPlayState(8, 0);
 88	CSND_SetPlayState(9, 0);
 89	csndExecCmds(0);
 90
 91	if (runner->context.gba->memory.hw.devices & HW_TILT) {
 92		HIDUSER_DisableAccelerometer();
 93	}
 94	if (runner->context.gba->memory.hw.devices & HW_GYRO) {
 95		HIDUSER_DisableGyroscope();
 96	}
 97}
 98
 99static void _drawFrame(struct GBAGUIRunner* runner, bool faded) {
100	GX_SetDisplayTransfer(0, renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202);
101	GSPGPU_FlushDataCache(0, tex->data, 256 * VIDEO_VERTICAL_PIXELS * 2);
102#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
103	if (hasSound) {
104		memset(&audioLeft[audioPos], 0, AUDIO_SAMPLES);
105		memset(&audioRight[audioPos], 0, AUDIO_SAMPLES);
106		size_t samples = blip_read_samples(runner->context.gba->audio.left, &audioLeft[audioPos], AUDIO_SAMPLES, false);
107		blip_read_samples(runner->context.gba->audio.right, &audioRight[audioPos], AUDIO_SAMPLES, false);
108		size_t audioPosNew = (audioPos + AUDIO_SAMPLES) % (AUDIO_SAMPLES * 2);
109		GSPGPU_FlushDataCache(0, (void*) audioLeft, AUDIO_SAMPLES * 2 * sizeof(int16_t));
110		GSPGPU_FlushDataCache(0, (void*) audioRight, AUDIO_SAMPLES * 2 * sizeof(int16_t));
111		csndPlaySound(0x8, SOUND_ONE_SHOT | SOUND_FORMAT_16BIT, 0x8000, 1.0, -1.0, &audioLeft[audioPos], &audioLeft[audioPosNew], samples * 2);
112		csndPlaySound(0x9, SOUND_ONE_SHOT | SOUND_FORMAT_16BIT, 0x8000, 1.0, 1.0, &audioRight[audioPos], &audioLeft[audioPosNew], samples * 2);
113		audioPos = audioPosNew;
114	} else {
115		blip_clear(runner->context.gba->audio.left);
116		blip_clear(runner->context.gba->audio.right);
117	}
118#endif
119	gspWaitForPPF();
120	_drawStart();
121	sf2d_draw_texture_scale_blend(tex, 40, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0));
122	_drawEnd();
123}
124
125static uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
126	hidScanInput();
127	uint32_t activeKeys = hidKeysHeld() & 0xF00003FF;
128	activeKeys |= activeKeys >> 24;
129	return activeKeys;
130}
131
132static int _pollInput(void) {
133	hidScanInput();
134	int keys = 0;
135	int activeKeys = hidKeysHeld();
136	if (activeKeys & KEY_X) {
137		keys |= 1 << GUI_INPUT_CANCEL;
138	}
139	if (activeKeys & KEY_B) {
140		keys |= 1 << GUI_INPUT_BACK;
141	}
142	if (activeKeys & KEY_A) {
143		keys |= 1 << GUI_INPUT_SELECT;
144	}
145	if (activeKeys & KEY_LEFT) {
146		keys |= 1 << GUI_INPUT_LEFT;
147	}
148	if (activeKeys & KEY_RIGHT) {
149		keys |= 1 << GUI_INPUT_RIGHT;
150	}
151	if (activeKeys & KEY_UP) {
152		keys |= 1 << GUI_INPUT_UP;
153	}
154	if (activeKeys & KEY_DOWN) {
155		keys |= 1 << GUI_INPUT_DOWN;
156	}
157	return keys;
158}
159
160static void _sampleRotation(struct GBARotationSource* source) {
161	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
162	// Work around ctrulib getting the entries wrong
163	rotation->accel = *(accelVector*)& hidSharedMem[0x48];
164	rotation->gyro = *(angularRate*)& hidSharedMem[0x5C];
165}
166
167static int32_t _readTiltX(struct GBARotationSource* source) {
168	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
169	return rotation->accel.x << 18L;
170}
171
172static int32_t _readTiltY(struct GBARotationSource* source) {
173	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
174	return rotation->accel.y << 18L;
175}
176
177static int32_t _readGyroZ(struct GBARotationSource* source) {
178	struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source;
179	return rotation->gyro.y << 18L; // Yes, y
180}
181
182int main() {
183	hasSound = !csndInit();
184
185	rotation.d.sample = _sampleRotation;
186	rotation.d.readTiltX = _readTiltX;
187	rotation.d.readTiltY = _readTiltY;
188	rotation.d.readGyroZ = _readGyroZ;
189
190	if (!allocateRomBuffer()) {
191		return 1;
192	}
193
194	if (hasSound) {
195		audioLeft = linearAlloc(AUDIO_SAMPLES * 2 * sizeof(int16_t));
196		audioRight = linearAlloc(AUDIO_SAMPLES * 2 * sizeof(int16_t));
197	}
198
199	sf2d_init();
200	sf2d_set_clear_color(0);
201	tex = sf2d_create_texture(256, 256, TEXFMT_RGB565, SF2D_PLACE_RAM);
202	memset(tex->data, 0, 256 * 256 * 2);
203
204	sdmcArchive = (FS_archive) {
205		ARCH_SDMC,
206		(FS_path) { PATH_EMPTY, 1, (const u8*)"" },
207		0, 0
208	};
209	FSUSER_OpenArchive(0, &sdmcArchive);
210
211	logFile = VFileOpen("/mgba.log", O_WRONLY | O_CREAT | O_TRUNC);
212	struct GUIFont* font = GUIFontCreate();
213
214	if (!font) {
215		goto cleanup;
216	}
217
218	struct GBAGUIRunner runner = {
219		.params = {
220			320, 240,
221			font, "/",
222			_drawStart, _drawEnd, _pollInput,
223			0, 0,
224
225			GUI_PARAMS_TRAIL
226		},
227		.setup = _setup,
228		.teardown = 0,
229		.gameLoaded = _gameLoaded,
230		.gameUnloaded = _gameUnloaded,
231		.prepareForFrame = 0,
232		.drawFrame = _drawFrame,
233		.pollGameInput = _pollGameInput
234	};
235	GBAGUIInit(&runner, 0);
236	GBAGUIRunloop(&runner);
237	GBAGUIDeinit(&runner);
238
239cleanup:
240	mappedMemoryFree(renderer.outputBuffer, 0);
241
242	if (logFile) {
243		logFile->close(logFile);
244	}
245
246	sf2d_free_texture(tex);
247	sf2d_fini();
248
249	if (hasSound) {
250		linearFree(audioLeft);
251		linearFree(audioRight);
252	}
253	csndExit();
254	return 0;
255}
256
257static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
258	UNUSED(thread);
259	UNUSED(level);
260	if (!logFile) {
261		return;
262	}
263	char out[256];
264	size_t len = vsnprintf(out, sizeof(out), format, args);
265	if (len >= sizeof(out)) {
266		len = sizeof(out) - 1;
267	}
268	out[len] = '\n';
269	logFile->write(logFile, out, len + 1);
270}