all repos — mgba @ 1b43c52a9e6168ec37dba428f599fb3b5d72870f

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