all repos — mgba @ e12ca74d1e891349a01be852e0ff555529868e50

mGBA Game Boy Advance Emulator

src/platform/libretro/libretro.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#include "libretro.h"
   7
   8#include <mgba-util/common.h>
   9
  10#include <mgba/core/blip_buf.h>
  11#include <mgba/core/cheats.h>
  12#include <mgba/core/core.h>
  13#include <mgba/core/log.h>
  14#include <mgba/core/serialize.h>
  15#include <mgba/core/version.h>
  16#ifdef M_CORE_GB
  17#include <mgba/gb/core.h>
  18#include <mgba/internal/gb/gb.h>
  19#include <mgba/internal/gb/mbc.h>
  20#endif
  21#ifdef M_CORE_GBA
  22#include <mgba/gba/core.h>
  23#include <mgba/gba/interface.h>
  24#include <mgba/internal/gba/gba.h>
  25#endif
  26#include <mgba-util/memory.h>
  27#include <mgba-util/vfs.h>
  28
  29#include "libretro_core_options.h"
  30
  31#define SAMPLES 1024
  32#define RUMBLE_PWM 35
  33#define EVENT_RATE 60
  34
  35static retro_environment_t environCallback;
  36static retro_video_refresh_t videoCallback;
  37static retro_audio_sample_batch_t audioCallback;
  38static retro_input_poll_t inputPollCallback;
  39static retro_input_state_t inputCallback;
  40static retro_log_printf_t logCallback;
  41static retro_set_rumble_state_t rumbleCallback;
  42static retro_sensor_get_input_t sensorGetCallback;
  43static retro_set_sensor_state_t sensorStateCallback;
  44
  45static void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
  46
  47static void _postAudioBuffer(struct mAVStream*, blip_t* left, blip_t* right);
  48static void _setRumble(struct mRumble* rumble, int enable);
  49static uint8_t _readLux(struct GBALuminanceSource* lux);
  50static void _updateLux(struct GBALuminanceSource* lux);
  51static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned height, size_t pitch);
  52static void _startImage(struct mImageSource*, unsigned w, unsigned h, int colorFormats);
  53static void _stopImage(struct mImageSource*);
  54static void _requestImage(struct mImageSource*, const void** buffer, size_t* stride, enum mColorFormat* colorFormat);
  55static void _updateRotation(struct mRotationSource* source);
  56static int32_t _readTiltX(struct mRotationSource* source);
  57static int32_t _readTiltY(struct mRotationSource* source);
  58static int32_t _readGyroZ(struct mRotationSource* source);
  59
  60static struct mCore* core;
  61static color_t* outputBuffer = NULL;
  62static void* data;
  63static size_t dataSize;
  64static void* savedata;
  65static struct mAVStream stream;
  66static bool sensorsInitDone;
  67static int rumbleUp;
  68static int rumbleDown;
  69static struct mRumble rumble;
  70static struct GBALuminanceSource lux;
  71static struct mRotationSource rotation;
  72static bool rotationEnabled;
  73static int luxLevelIndex;
  74static uint8_t luxLevel;
  75static bool luxSensorEnabled;
  76static bool luxSensorUsed;
  77static struct mLogger logger;
  78static struct retro_camera_callback cam;
  79static struct mImageSource imageSource;
  80static uint32_t* camData = NULL;
  81static unsigned camWidth;
  82static unsigned camHeight;
  83static unsigned imcapWidth;
  84static unsigned imcapHeight;
  85static size_t camStride;
  86static bool deferredSetup = false;
  87static bool envVarsUpdated;
  88static int32_t tiltX = 0;
  89static int32_t tiltY = 0;
  90static int32_t gyroZ = 0;
  91
  92static void _initSensors(void) {
  93	if (sensorsInitDone) {
  94		return;
  95	}
  96
  97	struct retro_sensor_interface sensorInterface;
  98	if (environCallback(RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE, &sensorInterface)) {
  99		sensorGetCallback = sensorInterface.get_sensor_input;
 100		sensorStateCallback = sensorInterface.set_sensor_state;
 101
 102		if (sensorStateCallback && sensorGetCallback) {
 103			if (sensorStateCallback(0, RETRO_SENSOR_ACCELEROMETER_ENABLE, EVENT_RATE)
 104				&& sensorStateCallback(0, RETRO_SENSOR_GYROSCOPE_ENABLE, EVENT_RATE)) {
 105				rotationEnabled = true;
 106			}
 107
 108			if (sensorStateCallback(0, RETRO_SENSOR_ILLUMINANCE_ENABLE, EVENT_RATE)) {
 109				luxSensorEnabled = true;
 110			}
 111		}
 112	}
 113
 114	sensorsInitDone = true;
 115}
 116
 117static void _reloadSettings(void) {
 118	struct mCoreOptions opts = {
 119		.useBios = true,
 120		.volume = 0x100,
 121	};
 122
 123	struct retro_variable var;
 124#ifdef M_CORE_GB
 125	enum GBModel model;
 126	const char* modelName;
 127
 128	var.key = "mgba_gb_model";
 129	var.value = 0;
 130	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 131		if (strcmp(var.value, "Game Boy") == 0) {
 132			model = GB_MODEL_DMG;
 133		} else if (strcmp(var.value, "Super Game Boy") == 0) {
 134			model = GB_MODEL_SGB;
 135		} else if (strcmp(var.value, "Game Boy Color") == 0) {
 136			model = GB_MODEL_CGB;
 137		} else if (strcmp(var.value, "Game Boy Advance") == 0) {
 138			model = GB_MODEL_AGB;
 139		} else {
 140			model = GB_MODEL_AUTODETECT;
 141		}
 142
 143		modelName = GBModelToName(model);
 144		mCoreConfigSetDefaultValue(&core->config, "gb.model", modelName);
 145		mCoreConfigSetDefaultValue(&core->config, "sgb.model", modelName);
 146		mCoreConfigSetDefaultValue(&core->config, "cgb.model", modelName);
 147	}
 148#endif
 149
 150	var.key = "mgba_use_bios";
 151	var.value = 0;
 152	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 153		opts.useBios = strcmp(var.value, "ON") == 0;
 154	}
 155
 156	var.key = "mgba_skip_bios";
 157	var.value = 0;
 158	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 159		opts.skipBios = strcmp(var.value, "ON") == 0;
 160	}
 161
 162#ifdef M_CORE_GB
 163	var.key = "mgba_sgb_borders";
 164	var.value = 0;
 165	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 166		mCoreConfigSetDefaultIntValue(&core->config, "sgb.borders", strcmp(var.value, "ON") == 0);
 167	}
 168#endif
 169
 170	var.key = "mgba_frameskip";
 171	var.value = 0;
 172	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 173		opts.frameskip = strtol(var.value, NULL, 10);
 174	}
 175
 176	var.key = "mgba_idle_optimization";
 177	var.value = 0;
 178	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 179		if (strcmp(var.value, "Don't Remove") == 0) {
 180			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "ignore");
 181		} else if (strcmp(var.value, "Remove Known") == 0) {
 182			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove");
 183		} else if (strcmp(var.value, "Detect and Remove") == 0) {
 184			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
 185		}
 186	}
 187
 188#ifdef M_CORE_GBA
 189	var.key = "mgba_force_gbp";
 190	var.value = 0;
 191	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 192		mCoreConfigSetDefaultIntValue(&core->config, "gba.forceGbp", strcmp(var.value, "ON") == 0);
 193	}
 194#endif
 195
 196	mCoreConfigLoadDefaults(&core->config, &opts);
 197	mCoreLoadConfig(core);
 198}
 199
 200static void _doDeferredSetup(void) {
 201	// Libretro API doesn't let you know when it's done copying data into the save buffers.
 202	// On the off-hand chance that a core actually expects its buffers to be populated when
 203	// you actually first get them, you're out of luck without workarounds. Yup, seriously.
 204	// Here's that workaround, but really the API needs to be thrown out and rewritten.
 205	struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
 206	if (!core->loadSave(core, save)) {
 207		save->close(save);
 208	}
 209	deferredSetup = false;
 210}
 211
 212unsigned retro_api_version(void) {
 213	return RETRO_API_VERSION;
 214}
 215
 216void retro_set_environment(retro_environment_t env) {
 217	environCallback = env;
 218
 219	libretro_set_core_options(environCallback);
 220}
 221
 222void retro_set_video_refresh(retro_video_refresh_t video) {
 223	videoCallback = video;
 224}
 225
 226void retro_set_audio_sample(retro_audio_sample_t audio) {
 227	UNUSED(audio);
 228}
 229
 230void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
 231	audioCallback = audioBatch;
 232}
 233
 234void retro_set_input_poll(retro_input_poll_t inputPoll) {
 235	inputPollCallback = inputPoll;
 236}
 237
 238void retro_set_input_state(retro_input_state_t input) {
 239	inputCallback = input;
 240}
 241
 242void retro_get_system_info(struct retro_system_info* info) {
 243	info->need_fullpath = false;
 244#ifdef M_CORE_GB
 245	info->valid_extensions = "gba|gb|gbc|sgb";
 246#else
 247	info->valid_extensions = "gba";
 248#endif
 249	info->library_version = projectVersion;
 250	info->library_name = projectName;
 251	info->block_extract = false;
 252}
 253
 254void retro_get_system_av_info(struct retro_system_av_info* info) {
 255	unsigned width, height;
 256	core->desiredVideoDimensions(core, &width, &height);
 257	info->geometry.base_width = width;
 258	info->geometry.base_height = height;
 259#ifdef M_CORE_GB
 260	if (core->platform(core) == mPLATFORM_GB) {
 261		info->geometry.max_width = 256;
 262		info->geometry.max_height = 224;
 263	} else
 264#endif
 265	{
 266		info->geometry.max_width = width;
 267		info->geometry.max_height = height;
 268	}
 269
 270	info->geometry.aspect_ratio = width / (double) height;
 271	info->timing.fps = core->frequency(core) / (float) core->frameCycles(core);
 272	info->timing.sample_rate = 32768;
 273}
 274
 275void retro_init(void) {
 276	enum retro_pixel_format fmt;
 277#ifdef COLOR_16_BIT
 278#ifdef COLOR_5_6_5
 279	fmt = RETRO_PIXEL_FORMAT_RGB565;
 280#else
 281#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
 282	fmt = RETRO_PIXEL_FORMAT_0RGB1555;
 283#endif
 284#else
 285#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
 286	fmt = RETRO_PIXEL_FORMAT_XRGB8888;
 287#endif
 288	environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
 289
 290	struct retro_input_descriptor inputDescriptors[] = {
 291		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
 292		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
 293		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
 294		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
 295		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
 296		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
 297		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
 298		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
 299		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
 300		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
 301		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
 302		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" },
 303		{ 0 }
 304	};
 305	environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
 306
 307	// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
 308
 309	struct retro_rumble_interface rumbleInterface;
 310	if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
 311		rumbleCallback = rumbleInterface.set_rumble_state;
 312		rumble.setRumble = _setRumble;
 313	} else {
 314		rumbleCallback = 0;
 315	}
 316
 317	sensorsInitDone = false;
 318	sensorGetCallback = 0;
 319	sensorStateCallback = 0;
 320
 321	rotationEnabled = false;
 322	rotation.sample = _updateRotation;
 323	rotation.readTiltX = _readTiltX;
 324	rotation.readTiltY = _readTiltY;
 325	rotation.readGyroZ = _readGyroZ;
 326
 327	envVarsUpdated = true;
 328	luxSensorUsed = false;
 329	luxSensorEnabled = false;
 330	luxLevelIndex = 0;
 331	luxLevel = 0;
 332	lux.readLuminance = _readLux;
 333	lux.sample = _updateLux;
 334	_updateLux(&lux);
 335
 336	struct retro_log_callback log;
 337	if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
 338		logCallback = log.log;
 339	} else {
 340		logCallback = 0;
 341	}
 342	logger.log = GBARetroLog;
 343	mLogSetDefaultLogger(&logger);
 344
 345	stream.videoDimensionsChanged = 0;
 346	stream.postAudioFrame = 0;
 347	stream.postAudioBuffer = _postAudioBuffer;
 348	stream.postVideoFrame = 0;
 349
 350	imageSource.startRequestImage = _startImage;
 351	imageSource.stopRequestImage = _stopImage;
 352	imageSource.requestImage = _requestImage;
 353}
 354
 355void retro_deinit(void) {
 356	free(outputBuffer);
 357
 358	if (sensorStateCallback) {
 359		sensorStateCallback(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, EVENT_RATE);
 360		sensorStateCallback(0, RETRO_SENSOR_GYROSCOPE_DISABLE, EVENT_RATE);
 361		sensorStateCallback(0, RETRO_SENSOR_ILLUMINANCE_DISABLE, EVENT_RATE);
 362		sensorGetCallback = NULL;
 363		sensorStateCallback = NULL;
 364	}
 365
 366	rotationEnabled = false;
 367	luxSensorEnabled = false;
 368	sensorsInitDone = false;
 369}
 370
 371void retro_run(void) {
 372	if (deferredSetup) {
 373		_doDeferredSetup();
 374	}
 375	uint16_t keys;
 376
 377	_initSensors();
 378	inputPollCallback();
 379
 380	bool updated = false;
 381	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
 382		envVarsUpdated = true;
 383
 384		struct retro_variable var = {
 385			.key = "mgba_allow_opposing_directions",
 386			.value = 0
 387		};
 388		if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 389			mCoreConfigSetIntValue(&core->config, "allowOpposingDirections", strcmp(var.value, "yes") == 0);
 390			core->reloadConfigOption(core, "allowOpposingDirections", NULL);
 391		}
 392
 393		var.key = "mgba_frameskip";
 394		var.value = 0;
 395		if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 396			mCoreConfigSetIntValue(&core->config, "frameskip", strtol(var.value, NULL, 10));
 397			core->reloadConfigOption(core, "frameskip", NULL);
 398		}
 399	}
 400
 401	keys = 0;
 402	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
 403	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
 404	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2;
 405	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3;
 406	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4;
 407	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5;
 408	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6;
 409	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;
 410	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
 411	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
 412	core->setKeys(core, keys);
 413
 414	if (!luxSensorUsed) {
 415		static bool wasAdjustingLux = false;
 416		if (wasAdjustingLux) {
 417			wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
 418			                  inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
 419		} else {
 420			if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
 421				++luxLevelIndex;
 422				if (luxLevelIndex > 10) {
 423					luxLevelIndex = 10;
 424				}
 425				wasAdjustingLux = true;
 426			} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
 427				--luxLevelIndex;
 428				if (luxLevelIndex < 0) {
 429					luxLevelIndex = 0;
 430				}
 431				wasAdjustingLux = true;
 432			}
 433		}
 434	}
 435
 436	core->runFrame(core);
 437	unsigned width, height;
 438	core->desiredVideoDimensions(core, &width, &height);
 439	videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
 440
 441	if (rumbleCallback) {
 442		if (rumbleUp) {
 443			rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
 444			rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
 445		} else {
 446			rumbleCallback(0, RETRO_RUMBLE_STRONG, 0);
 447			rumbleCallback(0, RETRO_RUMBLE_WEAK, 0);
 448		}
 449		rumbleUp = 0;
 450		rumbleDown = 0;
 451	}
 452}
 453
 454static void _setupMaps(struct mCore* core) {
 455#ifdef M_CORE_GBA
 456	if (core->platform(core) == mPLATFORM_GBA) {
 457		struct GBA* gba = core->board;
 458		struct retro_memory_descriptor descs[11];
 459		struct retro_memory_map mmaps;
 460		size_t romSize = gba->memory.romSize + (gba->memory.romSize & 1);
 461
 462		memset(descs, 0, sizeof(descs));
 463		size_t savedataSize = retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
 464
 465		/* Map internal working RAM */
 466		descs[0].ptr    = gba->memory.iwram;
 467		descs[0].start  = BASE_WORKING_IRAM;
 468		descs[0].len    = SIZE_WORKING_IRAM;
 469		descs[0].select = 0xFF000000;
 470
 471		/* Map working RAM */
 472		descs[1].ptr    = gba->memory.wram;
 473		descs[1].start  = BASE_WORKING_RAM;
 474		descs[1].len    = SIZE_WORKING_RAM;
 475		descs[1].select = 0xFF000000;
 476
 477		/* Map save RAM */
 478		/* TODO: if SRAM is flash, use start=0 addrspace="S" instead */
 479		descs[2].ptr    = savedataSize ? savedata : NULL;
 480		descs[2].start  = BASE_CART_SRAM;
 481		descs[2].len    = savedataSize;
 482
 483		/* Map ROM */
 484		descs[3].ptr    = gba->memory.rom;
 485		descs[3].start  = BASE_CART0;
 486		descs[3].len    = romSize;
 487		descs[3].flags  = RETRO_MEMDESC_CONST;
 488
 489		descs[4].ptr    = gba->memory.rom;
 490		descs[4].start  = BASE_CART1;
 491		descs[4].len    = romSize;
 492		descs[4].flags  = RETRO_MEMDESC_CONST;
 493
 494		descs[5].ptr    = gba->memory.rom;
 495		descs[5].start  = BASE_CART2;
 496		descs[5].len    = romSize;
 497		descs[5].flags  = RETRO_MEMDESC_CONST;
 498
 499		/* Map BIOS */
 500		descs[6].ptr    = gba->memory.bios;
 501		descs[6].start  = BASE_BIOS;
 502		descs[6].len    = SIZE_BIOS;
 503		descs[6].flags  = RETRO_MEMDESC_CONST;
 504
 505		/* Map VRAM */
 506		descs[7].ptr    = gba->video.vram;
 507		descs[7].start  = BASE_VRAM;
 508		descs[7].len    = SIZE_VRAM;
 509		descs[7].select = 0xFF000000;
 510
 511		/* Map palette RAM */
 512		descs[8].ptr    = gba->video.palette;
 513		descs[8].start  = BASE_PALETTE_RAM;
 514		descs[8].len    = SIZE_PALETTE_RAM;
 515		descs[8].select = 0xFF000000;
 516
 517		/* Map OAM */
 518		descs[9].ptr    = &gba->video.oam; /* video.oam is a structure */
 519		descs[9].start  = BASE_OAM;
 520		descs[9].len    = SIZE_OAM;
 521		descs[9].select = 0xFF000000;
 522
 523		/* Map mmapped I/O */
 524		descs[10].ptr    = gba->memory.io;
 525		descs[10].start  = BASE_IO;
 526		descs[10].len    = SIZE_IO;
 527
 528		mmaps.descriptors = descs;
 529		mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
 530
 531		bool yes = true;
 532		environCallback(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
 533		environCallback(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes);
 534	}
 535#endif
 536#ifdef M_CORE_GB
 537	if (core->platform(core) == mPLATFORM_GB) {
 538		struct GB* gb = core->board;
 539		struct retro_memory_descriptor descs[11];
 540		struct retro_memory_map mmaps;
 541
 542		memset(descs, 0, sizeof(descs));
 543		size_t savedataSize = retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
 544
 545		unsigned i = 0;
 546
 547		/* Map ROM */
 548		descs[i].ptr    = gb->memory.rom;
 549		descs[i].start  = GB_BASE_CART_BANK0;
 550		descs[i].len    = GB_SIZE_CART_BANK0;
 551		descs[i].flags  = RETRO_MEMDESC_CONST;
 552		i++;
 553
 554		descs[i].ptr    = gb->memory.rom;
 555		descs[i].offset = GB_SIZE_CART_BANK0;
 556		descs[i].start  = GB_BASE_CART_BANK1;
 557		descs[i].len    = GB_SIZE_CART_BANK0;
 558		descs[i].flags  = RETRO_MEMDESC_CONST;
 559		i++;
 560
 561		/* Map VRAM */
 562		descs[i].ptr    = gb->video.vram;
 563		descs[i].start  = GB_BASE_VRAM;
 564		descs[i].len    = GB_SIZE_VRAM_BANK0;
 565		i++;
 566
 567		/* Map working RAM */
 568		descs[i].ptr    = gb->memory.wram;
 569		descs[i].start  = GB_BASE_WORKING_RAM_BANK0;
 570		descs[i].len    = GB_SIZE_WORKING_RAM_BANK0;
 571		i++;
 572
 573		descs[i].ptr    = gb->memory.wram;
 574		descs[i].offset = GB_SIZE_WORKING_RAM_BANK0;
 575		descs[i].start  = GB_BASE_WORKING_RAM_BANK1;
 576		descs[i].len    = GB_SIZE_WORKING_RAM_BANK0;
 577		i++;
 578
 579		/* Map OAM */
 580		descs[i].ptr    = &gb->video.oam; /* video.oam is a structure */
 581		descs[i].start  = GB_BASE_OAM;
 582		descs[i].len    = GB_SIZE_OAM;
 583		descs[i].select = 0xFFFFFF60;
 584		i++;
 585
 586		/* Map mmapped I/O */
 587		descs[i].ptr    = gb->memory.io;
 588		descs[i].start  = GB_BASE_IO;
 589		descs[i].len    = GB_SIZE_IO;
 590		i++;
 591
 592		/* Map High RAM */
 593		descs[i].ptr    = gb->memory.hram;
 594		descs[i].start  = GB_BASE_HRAM;
 595		descs[i].len    = GB_SIZE_HRAM;
 596		descs[i].select = 0xFFFFFF80;
 597		i++;
 598
 599		/* Map IE Register */
 600		descs[i].ptr    = &gb->memory.ie;
 601		descs[i].start  = GB_BASE_IE;
 602		descs[i].len    = 1;
 603		i++;
 604
 605		/* Map External RAM */
 606		if (gb->memory.sram) {
 607			descs[i].ptr    = gb->memory.sram;
 608			descs[i].start  = GB_BASE_EXTERNAL_RAM;
 609			descs[i].len    = savedataSize;
 610			i++;
 611		}
 612
 613		if (gb->model >= GB_MODEL_CGB) {
 614			/* Map working RAM */
 615			/* banks 2-7 of wram mapped in virtual address so it can be
 616			 * accessed without bank switching, GBC only */
 617			descs[i].ptr    = gb->memory.wram + 0x2000;
 618			descs[i].start  = 0x10000;
 619			descs[i].len    = GB_SIZE_WORKING_RAM - 0x2000;
 620			descs[i].select = 0xFFFFA000;
 621			i++;
 622		}
 623
 624		mmaps.descriptors = descs;
 625		mmaps.num_descriptors = i;
 626
 627		bool yes = true;
 628		environCallback(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
 629		environCallback(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes);
 630	}
 631#endif
 632}
 633
 634void retro_reset(void) {
 635	core->reset(core);
 636	_setupMaps(core);
 637
 638	rumbleUp = 0;
 639	rumbleDown = 0;
 640}
 641
 642bool retro_load_game(const struct retro_game_info* game) {
 643	struct VFile* rom;
 644	if (game->data) {
 645		data = anonymousMemoryMap(game->size);
 646		dataSize = game->size;
 647		memcpy(data, game->data, game->size);
 648		rom = VFileFromMemory(data, game->size);
 649	} else {
 650		data = 0;
 651		rom = VFileOpen(game->path, O_RDONLY);
 652	}
 653	if (!rom) {
 654		return false;
 655	}
 656
 657	core = mCoreFindVF(rom);
 658	if (!core) {
 659		rom->close(rom);
 660		mappedMemoryFree(data, game->size);
 661		return false;
 662	}
 663	mCoreInitConfig(core, NULL);
 664	core->init(core);
 665	core->setAVStream(core, &stream);
 666
 667	size_t size = 256 * 224 * BYTES_PER_PIXEL;
 668	outputBuffer = malloc(size);
 669	memset(outputBuffer, 0xFF, size);
 670	core->setVideoBuffer(core, outputBuffer, 256);
 671
 672	core->setAudioBufferSize(core, SAMPLES);
 673
 674	blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768);
 675	blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768);
 676
 677	core->setPeripheral(core, mPERIPH_RUMBLE, &rumble);
 678
 679	savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
 680	memset(savedata, 0xFF, SIZE_CART_FLASH1M);
 681
 682	_reloadSettings();
 683	core->loadROM(core, rom);
 684	deferredSetup = true;
 685
 686	const char* sysDir = 0;
 687	const char* biosName = 0;
 688	char biosPath[PATH_MAX];
 689	environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir);
 690
 691#ifdef M_CORE_GBA
 692	if (core->platform(core) == mPLATFORM_GBA) {
 693		core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &lux);
 694		biosName = "gba_bios.bin";
 695
 696	}
 697#endif
 698
 699#ifdef M_CORE_GB
 700	if (core->platform(core) == mPLATFORM_GB) {
 701		memset(&cam, 0, sizeof(cam));
 702		cam.height = GBCAM_HEIGHT;
 703		cam.width = GBCAM_WIDTH;
 704		cam.caps = 1 << RETRO_CAMERA_BUFFER_RAW_FRAMEBUFFER;
 705		cam.frame_raw_framebuffer = _updateCamera;
 706		if (environCallback(RETRO_ENVIRONMENT_GET_CAMERA_INTERFACE, &cam)) {
 707			core->setPeripheral(core, mPERIPH_IMAGE_SOURCE, &imageSource);
 708		}
 709
 710		const char* modelName = mCoreConfigGetValue(&core->config, "gb.model");
 711		struct GB* gb = core->board;
 712
 713		if (modelName) {
 714			gb->model = GBNameToModel(modelName);
 715		} else {
 716			GBDetectModel(gb);
 717		}
 718
 719		switch (gb->model) {
 720		case GB_MODEL_AGB:
 721		case GB_MODEL_CGB:
 722			biosName = "gbc_bios.bin";
 723			break;
 724		case GB_MODEL_SGB:
 725			biosName = "sgb_bios.bin";
 726			break;
 727		case GB_MODEL_DMG:
 728		default:
 729			biosName = "gb_bios.bin";
 730			break;
 731		}
 732	}
 733#endif
 734
 735	if (core->opts.useBios && sysDir && biosName) {
 736		snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, biosName);
 737		struct VFile* bios = VFileOpen(biosPath, O_RDONLY);
 738		if (bios) {
 739			core->loadBIOS(core, bios, 0);
 740		}
 741	}
 742
 743	core->reset(core);
 744	_setupMaps(core);
 745
 746	return true;
 747}
 748
 749void retro_unload_game(void) {
 750	if (!core) {
 751		return;
 752	}
 753	mCoreConfigDeinit(&core->config);
 754	core->deinit(core);
 755	mappedMemoryFree(data, dataSize);
 756	data = 0;
 757	mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
 758	savedata = 0;
 759}
 760
 761size_t retro_serialize_size(void) {
 762	if (deferredSetup) {
 763		_doDeferredSetup();
 764	}
 765	struct VFile* vfm = VFileMemChunk(NULL, 0);
 766	mCoreSaveStateNamed(core, vfm, SAVESTATE_SAVEDATA | SAVESTATE_RTC);
 767	size_t size = vfm->size(vfm);
 768	vfm->close(vfm);
 769	return size;
 770}
 771
 772bool retro_serialize(void* data, size_t size) {
 773	if (deferredSetup) {
 774		_doDeferredSetup();
 775	}
 776	struct VFile* vfm = VFileMemChunk(NULL, 0);
 777	mCoreSaveStateNamed(core, vfm, SAVESTATE_SAVEDATA | SAVESTATE_RTC);
 778	if ((ssize_t) size > vfm->size(vfm)) {
 779		size = vfm->size(vfm);
 780	} else if ((ssize_t) size < vfm->size(vfm)) {
 781		vfm->close(vfm);
 782		return false;
 783	}
 784	vfm->seek(vfm, 0, SEEK_SET);
 785	vfm->read(vfm, data, size);
 786	vfm->close(vfm);
 787	return true;
 788}
 789
 790bool retro_unserialize(const void* data, size_t size) {
 791	if (deferredSetup) {
 792		_doDeferredSetup();
 793	}
 794	struct VFile* vfm = VFileFromConstMemory(data, size);
 795	bool success = mCoreLoadStateNamed(core, vfm, SAVESTATE_RTC);
 796	vfm->close(vfm);
 797	return success;
 798}
 799
 800void retro_cheat_reset(void) {
 801	mCheatDeviceClear(core->cheatDevice(core));
 802}
 803
 804void retro_cheat_set(unsigned index, bool enabled, const char* code) {
 805	UNUSED(index);
 806	UNUSED(enabled);
 807	struct mCheatDevice* device = core->cheatDevice(core);
 808	struct mCheatSet* cheatSet = NULL;
 809	if (mCheatSetsSize(&device->cheats)) {
 810		cheatSet = *mCheatSetsGetPointer(&device->cheats, 0);
 811	} else {
 812		cheatSet = device->createSet(device, NULL);
 813		mCheatAddSet(device, cheatSet);
 814	}
 815// Convert the super wonky unportable libretro format to something normal
 816#ifdef M_CORE_GBA
 817	if (core->platform(core) == mPLATFORM_GBA) {
 818		char realCode[] = "XXXXXXXX XXXXXXXX";
 819		size_t len = strlen(code) + 1; // Include null terminator
 820		size_t i, pos;
 821		for (i = 0, pos = 0; i < len; ++i) {
 822			if (isspace((int) code[i]) || code[i] == '+') {
 823				realCode[pos] = ' ';
 824			} else {
 825				realCode[pos] = code[i];
 826			}
 827			if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) {
 828				realCode[pos] = '\0';
 829				mCheatAddLine(cheatSet, realCode, 0);
 830				pos = 0;
 831				continue;
 832			}
 833			++pos;
 834		}
 835	}
 836#endif
 837#ifdef M_CORE_GB
 838	if (core->platform(core) == mPLATFORM_GB) {
 839		char realCode[] = "XXX-XXX-XXX";
 840		size_t len = strlen(code) + 1; // Include null terminator
 841		size_t i, pos;
 842		for (i = 0, pos = 0; i < len; ++i) {
 843			if (isspace((int) code[i]) || code[i] == '+') {
 844				realCode[pos] = '\0';
 845			} else {
 846				realCode[pos] = code[i];
 847			}
 848			if (pos == 11 || !realCode[pos]) {
 849				realCode[pos] = '\0';
 850				mCheatAddLine(cheatSet, realCode, 0);
 851				pos = 0;
 852				continue;
 853			}
 854			++pos;
 855		}
 856	}
 857#endif
 858	cheatSet->refresh(cheatSet, device);
 859}
 860
 861unsigned retro_get_region(void) {
 862	return RETRO_REGION_NTSC; // TODO: This isn't strictly true
 863}
 864
 865void retro_set_controller_port_device(unsigned port, unsigned device) {
 866	UNUSED(port);
 867	UNUSED(device);
 868}
 869
 870bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
 871	UNUSED(game_type);
 872	UNUSED(info);
 873	UNUSED(num_info);
 874	return false;
 875}
 876
 877void* retro_get_memory_data(unsigned id) {
 878	switch (id) {
 879	case RETRO_MEMORY_SAVE_RAM:
 880		return savedata;
 881	case RETRO_MEMORY_RTC:
 882		switch (core->platform(core)) {
 883#ifdef M_CORE_GB
 884		case mPLATFORM_GB:
 885			switch (((struct GB*) core->board)->memory.mbcType) {
 886			case GB_MBC3_RTC:
 887				return &((uint8_t*) savedata)[((struct GB*) core->board)->sramSize];
 888			default:
 889				return NULL;
 890			}
 891#endif
 892		default:
 893			return NULL;
 894		}
 895	default:
 896		break;
 897	}
 898	return NULL;
 899}
 900
 901size_t retro_get_memory_size(unsigned id) {
 902	switch (id) {
 903	case RETRO_MEMORY_SAVE_RAM:
 904		switch (core->platform(core)) {
 905#ifdef M_CORE_GBA
 906		case mPLATFORM_GBA:
 907			switch (((struct GBA*) core->board)->memory.savedata.type) {
 908			case SAVEDATA_AUTODETECT:
 909				return SIZE_CART_FLASH1M;
 910			default:
 911				return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
 912			}
 913#endif
 914#ifdef M_CORE_GB
 915		case mPLATFORM_GB:
 916			return ((struct GB*) core->board)->sramSize;
 917#endif
 918		default:
 919			break;
 920		}
 921		break;
 922	case RETRO_MEMORY_RTC:
 923		switch (core->platform(core)) {
 924#ifdef M_CORE_GB
 925		case mPLATFORM_GB:
 926			switch (((struct GB*) core->board)->memory.mbcType) {
 927			case GB_MBC3_RTC:
 928				return sizeof(struct GBMBCRTCSaveBuffer);
 929			default:
 930				return 0;
 931			}
 932#endif
 933		default:
 934			break;
 935		}
 936		break;
 937	default:
 938		break;
 939	}
 940	return 0;
 941}
 942
 943void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 944	UNUSED(logger);
 945	if (!logCallback) {
 946		return;
 947	}
 948
 949	char message[128];
 950	vsnprintf(message, sizeof(message), format, args);
 951
 952	enum retro_log_level retroLevel = RETRO_LOG_INFO;
 953	switch (level) {
 954	case mLOG_ERROR:
 955	case mLOG_FATAL:
 956		retroLevel = RETRO_LOG_ERROR;
 957		break;
 958	case mLOG_WARN:
 959		retroLevel = RETRO_LOG_WARN;
 960		break;
 961	case mLOG_INFO:
 962		retroLevel = RETRO_LOG_INFO;
 963		break;
 964	case mLOG_GAME_ERROR:
 965	case mLOG_STUB:
 966#ifdef NDEBUG
 967		return;
 968#else
 969		retroLevel = RETRO_LOG_DEBUG;
 970		break;
 971#endif
 972	case mLOG_DEBUG:
 973		retroLevel = RETRO_LOG_DEBUG;
 974		break;
 975	}
 976#ifdef NDEBUG
 977	static int biosCat = -1;
 978	if (biosCat < 0) {
 979		biosCat = mLogCategoryById("gba.bios");
 980	}
 981
 982	if (category == biosCat) {
 983		return;
 984	}
 985#endif
 986	logCallback(retroLevel, "%s: %s\n", mLogCategoryName(category), message);
 987}
 988
 989static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
 990	UNUSED(stream);
 991	int16_t samples[SAMPLES * 2];
 992	blip_read_samples(left, samples, SAMPLES, true);
 993	blip_read_samples(right, samples + 1, SAMPLES, true);
 994	audioCallback(samples, SAMPLES);
 995}
 996
 997static void _setRumble(struct mRumble* rumble, int enable) {
 998	UNUSED(rumble);
 999	if (!rumbleCallback) {
1000		return;
1001	}
1002	if (enable) {
1003		++rumbleUp;
1004	} else {
1005		++rumbleDown;
1006	}
1007}
1008
1009static void _updateLux(struct GBALuminanceSource* lux) {
1010	UNUSED(lux);
1011	struct retro_variable var = {
1012		.key = "mgba_solar_sensor_level",
1013		.value = 0
1014	};
1015	bool luxVarUpdated = envVarsUpdated;
1016
1017	if (luxVarUpdated && (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value)) {
1018		luxVarUpdated = false;
1019	}
1020
1021	if (luxVarUpdated) {
1022		luxSensorUsed = strcmp(var.value, "sensor") == 0;
1023	}
1024
1025	if (luxSensorUsed) {
1026		float fLux = luxSensorEnabled ? sensorGetCallback(0, RETRO_SENSOR_ILLUMINANCE) : 0.0f;
1027		luxLevel = cbrtf(fLux) * 8;
1028	} else {
1029		if (luxVarUpdated) {
1030			char* end;
1031			int newLuxLevelIndex = strtol(var.value, &end, 10);
1032
1033			if (!*end) {
1034				if (newLuxLevelIndex > 10) {
1035					luxLevelIndex = 10;
1036				} else if (newLuxLevelIndex < 0) {
1037					luxLevelIndex = 0;
1038				} else {
1039					luxLevelIndex = newLuxLevelIndex;
1040				}
1041			}
1042		}
1043
1044		luxLevel = 0x16;
1045		if (luxLevelIndex > 0) {
1046			luxLevel += GBA_LUX_LEVELS[luxLevelIndex - 1];
1047		}
1048	}
1049
1050	envVarsUpdated = false;
1051}
1052
1053static uint8_t _readLux(struct GBALuminanceSource* lux) {
1054	UNUSED(lux);
1055	return 0xFF - luxLevel;
1056}
1057
1058static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned height, size_t pitch) {
1059	if (!camData || width > camWidth || height > camHeight) {
1060		if (camData) {
1061			free(camData);
1062			camData = NULL;
1063		}
1064		unsigned bufPitch = pitch / sizeof(*buffer);
1065		unsigned bufHeight = height;
1066		if (imcapWidth > bufPitch) {
1067			bufPitch = imcapWidth;
1068		}
1069		if (imcapHeight > bufHeight) {
1070			bufHeight = imcapHeight;
1071		}
1072		camData = malloc(sizeof(*buffer) * bufHeight * bufPitch);
1073		memset(camData, 0xFF, sizeof(*buffer) * bufHeight * bufPitch);
1074		camWidth = width;
1075		camHeight = bufHeight;
1076		camStride = bufPitch;
1077	}
1078	size_t i;
1079	for (i = 0; i < height; ++i) {
1080		memcpy(&camData[camStride * i], &buffer[pitch * i / sizeof(*buffer)], pitch);
1081	}
1082}
1083
1084static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int colorFormats) {
1085	UNUSED(image);
1086	UNUSED(colorFormats);
1087
1088	if (camData) {
1089		free(camData);
1090	}
1091	camData = NULL;
1092	imcapWidth = w;
1093	imcapHeight = h;
1094	cam.start();
1095}
1096
1097static void _stopImage(struct mImageSource* image) {
1098	UNUSED(image);
1099	cam.stop();	
1100}
1101
1102static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
1103	UNUSED(image);
1104	if (!camData) {
1105		cam.start();
1106		*buffer = NULL;
1107		return;
1108	}
1109	size_t offset = 0;
1110	if (imcapWidth < camWidth) {
1111		offset += (camWidth - imcapWidth) / 2;
1112	}
1113	if (imcapHeight < camHeight) {
1114		offset += (camHeight - imcapHeight) / 2 * camStride;
1115	}
1116
1117	*buffer = &camData[offset];
1118	*stride = camStride;
1119	*colorFormat = mCOLOR_XRGB8;
1120}
1121
1122static void _updateRotation(struct mRotationSource* source) {
1123	UNUSED(source);
1124	tiltX = 0;
1125	tiltY = 0;
1126	gyroZ = 0;
1127	if (rotationEnabled) {
1128		tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f;
1129		tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f;
1130		gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -1.1e9f;
1131	}
1132}
1133
1134static int32_t _readTiltX(struct mRotationSource* source) {
1135	UNUSED(source);
1136	return tiltX;
1137}
1138
1139static int32_t _readTiltY(struct mRotationSource* source) {
1140	UNUSED(source);
1141	return tiltY;
1142}
1143
1144static int32_t _readGyroZ(struct mRotationSource* source) {
1145	UNUSED(source);
1146	return gyroZ;
1147}