all repos — mgba @ ba6e34fd9039abb2a015e20449a3d47fa6b6f986

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