all repos — mgba @ ffbf509f5a75bec09969b454ec4eb363c3557f10

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/version.h>
 15#ifdef M_CORE_GB
 16#include <mgba/gb/core.h>
 17#include <mgba/internal/gb/gb.h>
 18#endif
 19#ifdef M_CORE_GBA
 20#include <mgba/gba/core.h>
 21#include <mgba/gba/interface.h>
 22#include <mgba/internal/gba/gba.h>
 23#endif
 24#include <mgba-util/circle-buffer.h>
 25#include <mgba-util/memory.h>
 26#include <mgba-util/vfs.h>
 27
 28#define SAMPLES 1024
 29#define RUMBLE_PWM 35
 30
 31static retro_environment_t environCallback;
 32static retro_video_refresh_t videoCallback;
 33static retro_audio_sample_batch_t audioCallback;
 34static retro_input_poll_t inputPollCallback;
 35static retro_input_state_t inputCallback;
 36static retro_log_printf_t logCallback;
 37static retro_set_rumble_state_t rumbleCallback;
 38
 39static void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
 40
 41static void _postAudioBuffer(struct mAVStream*, blip_t* left, blip_t* right);
 42static void _setRumble(struct mRumble* rumble, int enable);
 43static uint8_t _readLux(struct GBALuminanceSource* lux);
 44static void _updateLux(struct GBALuminanceSource* lux);
 45
 46static struct mCore* core;
 47static void* outputBuffer;
 48static void* data;
 49static size_t dataSize;
 50static void* savedata;
 51static struct mAVStream stream;
 52static int rumbleLevel;
 53static struct CircleBuffer rumbleHistory;
 54static struct mRumble rumble;
 55static struct GBALuminanceSource lux;
 56static int luxLevel;
 57static struct mLogger logger;
 58
 59static void _reloadSettings(void) {
 60	struct mCoreOptions opts = {
 61		.useBios = true,
 62		.volume = 0x100,
 63	};
 64
 65	struct retro_variable var;
 66	enum GBModel model;
 67	const char* modelName;
 68
 69	var.key = "mgba_model";
 70	var.value = 0;
 71	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 72		if (strcmp(var.value, "Game Boy") == 0) {
 73			model = GB_MODEL_DMG;
 74		} else if (strcmp(var.value, "Super Game Boy") == 0) {
 75			model = GB_MODEL_SGB;
 76		} else if (strcmp(var.value, "Game Boy Color") == 0) {
 77			model = GB_MODEL_CGB;
 78		} else {
 79			model = GB_MODEL_AUTODETECT;
 80		}
 81
 82		modelName = GBModelToName(model);
 83		mCoreConfigSetDefaultValue(&core->config, "gb.model", modelName);
 84		mCoreConfigSetDefaultValue(&core->config, "sgb.model", modelName);
 85		mCoreConfigSetDefaultValue(&core->config, "cgb.model", modelName);
 86	}
 87
 88	var.key = "mgba_use_bios";
 89	var.value = 0;
 90	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 91		opts.useBios = strcmp(var.value, "ON") == 0;
 92	}
 93
 94	var.key = "mgba_skip_bios";
 95	var.value = 0;
 96	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
 97		opts.skipBios = strcmp(var.value, "ON") == 0;
 98	}
 99
100	var.key = "mgba_sgb_borders";
101	var.value = 0;
102	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
103		if (strcmp(var.value, "ON") == 0) {
104			mCoreConfigSetDefaultIntValue(&core->config, "sgb.borders", true);
105		} else {
106			mCoreConfigSetDefaultIntValue(&core->config, "sgb.borders", false);
107		}
108	}
109
110	var.key = "mgba_idle_optimization";
111	var.value = 0;
112	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
113		if (strcmp(var.value, "Don't Remove") == 0) {
114			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "ignore");
115		} else if (strcmp(var.value, "Remove Known") == 0) {
116			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "remove");
117		} else if (strcmp(var.value, "Detect and Remove") == 0) {
118			mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
119		}
120	}
121
122	var.key = "mgba_frameskip";
123	var.value = 0;
124	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
125		opts.frameskip = strtol(var.value, NULL, 10);
126
127	}
128
129	mCoreConfigLoadDefaults(&core->config, &opts);
130	mCoreLoadConfig(core);
131}
132
133unsigned retro_api_version(void) {
134	return RETRO_API_VERSION;
135}
136
137void retro_set_environment(retro_environment_t env) {
138	environCallback = env;
139
140	struct retro_variable vars[] = {
141		{ "mgba_solar_sensor_level", "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
142		{ "mgba_allow_opposing_directions", "Allow opposing directional input; OFF|ON" },
143		{ "mgba_model", "Game Boy model (requires restart); Autodetect|Game Boy|Super Game Boy|Game Boy Color" },
144		{ "mgba_use_bios", "Use BIOS file if found (requires restart); ON|OFF" },
145		{ "mgba_skip_bios", "Skip BIOS intro (requires restart); OFF|ON" },
146		{ "mgba_sgb_borders", "Use Super Game Boy borders (requires restart); ON|OFF" },
147		{ "mgba_idle_optimization", "Idle loop removal; Remove Known|Detect and Remove|Don't Remove" },
148		{ "mgba_frameskip", "Frameskip; 0|1|2|3|4|5|6|7|8|9|10" },
149		{ 0, 0 }
150	};
151
152	environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars);
153}
154
155void retro_set_video_refresh(retro_video_refresh_t video) {
156	videoCallback = video;
157}
158
159void retro_set_audio_sample(retro_audio_sample_t audio) {
160	UNUSED(audio);
161}
162
163void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
164	audioCallback = audioBatch;
165}
166
167void retro_set_input_poll(retro_input_poll_t inputPoll) {
168	inputPollCallback = inputPoll;
169}
170
171void retro_set_input_state(retro_input_state_t input) {
172	inputCallback = input;
173}
174
175void retro_get_system_info(struct retro_system_info* info) {
176	info->need_fullpath = false;
177	info->valid_extensions = "gba|gb|gbc";
178	info->library_version = projectVersion;
179	info->library_name = projectName;
180	info->block_extract = false;
181}
182
183void retro_get_system_av_info(struct retro_system_av_info* info) {
184	unsigned width, height;
185	core->desiredVideoDimensions(core, &width, &height);
186	info->geometry.base_width = width;
187	info->geometry.base_height = height;
188	info->geometry.max_width = width;
189	info->geometry.max_height = height;
190	info->geometry.aspect_ratio = width / (double) height;
191	info->timing.fps = core->frequency(core) / (float) core->frameCycles(core);
192	info->timing.sample_rate = 32768;
193}
194
195void retro_init(void) {
196	enum retro_pixel_format fmt;
197#ifdef COLOR_16_BIT
198#ifdef COLOR_5_6_5
199	fmt = RETRO_PIXEL_FORMAT_RGB565;
200#else
201#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
202	fmt = RETRO_PIXEL_FORMAT_0RGB1555;
203#endif
204#else
205#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
206	fmt = RETRO_PIXEL_FORMAT_XRGB8888;
207#endif
208	environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
209
210	struct retro_input_descriptor inputDescriptors[] = {
211		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
212		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
213		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
214		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
215		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
216		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
217		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
218		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
219		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
220		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
221		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
222		{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" },
223		{ 0 }
224	};
225	environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
226
227	// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
228
229	struct retro_rumble_interface rumbleInterface;
230	if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
231		rumbleCallback = rumbleInterface.set_rumble_state;
232		CircleBufferInit(&rumbleHistory, RUMBLE_PWM);
233		rumble.setRumble = _setRumble;
234	} else {
235		rumbleCallback = 0;
236	}
237
238	luxLevel = 0;
239	lux.readLuminance = _readLux;
240	lux.sample = _updateLux;
241	_updateLux(&lux);
242
243	struct retro_log_callback log;
244	if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
245		logCallback = log.log;
246	} else {
247		logCallback = 0;
248	}
249	logger.log = GBARetroLog;
250	mLogSetDefaultLogger(&logger);
251
252	stream.videoDimensionsChanged = 0;
253	stream.postAudioFrame = 0;
254	stream.postAudioBuffer = _postAudioBuffer;
255	stream.postVideoFrame = 0;
256}
257
258void retro_deinit(void) {
259	free(outputBuffer);
260}
261
262void retro_run(void) {
263	uint16_t keys;
264	inputPollCallback();
265
266	bool updated = false;
267	if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
268		struct retro_variable var = {
269			.key = "mgba_allow_opposing_directions",
270			.value = 0
271		};
272		if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
273			((struct GBA*) core->board)->allowOpposingDirections = strcmp(var.value, "yes") == 0;
274		}
275
276		var.key = "mgba_frameskip";
277		var.value = 0;
278		if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
279			mCoreConfigSetUIntValue(&core->config, "frameskip", strtol(var.value, NULL, 10));
280			mCoreLoadConfig(core);
281		}
282	}
283
284	keys = 0;
285	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
286	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
287	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2;
288	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3;
289	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4;
290	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5;
291	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6;
292	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;
293	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
294	keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
295	core->setKeys(core, keys);
296
297	static bool wasAdjustingLux = false;
298	if (wasAdjustingLux) {
299		wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
300		                  inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
301	} else {
302		if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
303			++luxLevel;
304			if (luxLevel > 10) {
305				luxLevel = 10;
306			}
307			wasAdjustingLux = true;
308		} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
309			--luxLevel;
310			if (luxLevel < 0) {
311				luxLevel = 0;
312			}
313			wasAdjustingLux = true;
314		}
315	}
316
317	core->runFrame(core);
318	unsigned width, height;
319	core->desiredVideoDimensions(core, &width, &height);
320	videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
321}
322
323void static _setupMaps(struct mCore* core) {
324#ifdef M_CORE_GBA
325	if (core->platform(core) == PLATFORM_GBA) {
326		struct GBA* gba = core->board;
327		struct retro_memory_descriptor descs[11];
328		struct retro_memory_map mmaps;
329		size_t romSize = gba->memory.romSize + (gba->memory.romSize & 1);
330
331		memset(descs, 0, sizeof(descs));
332		size_t savedataSize = retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
333
334		/* Map internal working RAM */
335		descs[0].ptr    = gba->memory.iwram;
336		descs[0].start  = BASE_WORKING_IRAM;
337		descs[0].len    = SIZE_WORKING_IRAM;
338		descs[0].select = 0xFF000000;
339
340		/* Map working RAM */
341		descs[1].ptr    = gba->memory.wram;
342		descs[1].start  = BASE_WORKING_RAM;
343		descs[1].len    = SIZE_WORKING_RAM;
344		descs[1].select = 0xFF000000;
345
346		/* Map save RAM */
347		/* TODO: if SRAM is flash, use start=0 addrspace="S" instead */
348		descs[2].ptr    = savedataSize ? savedata : NULL;
349		descs[2].start  = BASE_CART_SRAM;
350		descs[2].len    = savedataSize;
351
352		/* Map ROM */
353		descs[3].ptr    = gba->memory.rom;
354		descs[3].start  = BASE_CART0;
355		descs[3].len    = romSize;
356		descs[3].flags  = RETRO_MEMDESC_CONST;
357
358		descs[4].ptr    = gba->memory.rom;
359		descs[4].start  = BASE_CART1;
360		descs[4].len    = romSize;
361		descs[4].flags  = RETRO_MEMDESC_CONST;
362
363		descs[5].ptr    = gba->memory.rom;
364		descs[5].start  = BASE_CART2;
365		descs[5].len    = romSize;
366		descs[5].flags  = RETRO_MEMDESC_CONST;
367
368		/* Map BIOS */
369		descs[6].ptr    = gba->memory.bios;
370		descs[6].start  = BASE_BIOS;
371		descs[6].len    = SIZE_BIOS;
372		descs[6].flags  = RETRO_MEMDESC_CONST;
373
374		/* Map VRAM */
375		descs[7].ptr    = gba->video.vram;
376		descs[7].start  = BASE_VRAM;
377		descs[7].len    = SIZE_VRAM;
378		descs[7].select = 0xFF000000;
379
380		/* Map palette RAM */
381		descs[8].ptr    = gba->video.palette;
382		descs[8].start  = BASE_PALETTE_RAM;
383		descs[8].len    = SIZE_PALETTE_RAM;
384		descs[8].select = 0xFF000000;
385
386		/* Map OAM */
387		descs[9].ptr    = &gba->video.oam; /* video.oam is a structure */
388		descs[9].start  = BASE_OAM;
389		descs[9].len    = SIZE_OAM;
390		descs[9].select = 0xFF000000;
391
392		/* Map mmapped I/O */
393		descs[10].ptr    = gba->memory.io;
394		descs[10].start  = BASE_IO;
395		descs[10].len    = SIZE_IO;
396
397		mmaps.descriptors = descs;
398		mmaps.num_descriptors = sizeof(descs) / sizeof(descs[0]);
399
400		bool yes = true;
401		environCallback(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
402		environCallback(RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS, &yes);
403	}
404#endif
405}
406
407void retro_reset(void) {
408	core->reset(core);
409	_setupMaps(core);
410
411	if (rumbleCallback) {
412		CircleBufferClear(&rumbleHistory);
413	}
414}
415
416bool retro_load_game(const struct retro_game_info* game) {
417	struct VFile* rom;
418	if (game->data) {
419		data = anonymousMemoryMap(game->size);
420		dataSize = game->size;
421		memcpy(data, game->data, game->size);
422		rom = VFileFromMemory(data, game->size);
423	} else {
424		data = 0;
425		rom = VFileOpen(game->path, O_RDONLY);
426	}
427	if (!rom) {
428		return false;
429	}
430
431	core = mCoreFindVF(rom);
432	if (!core) {
433		rom->close(rom);
434		mappedMemoryFree(data, game->size);
435		return false;
436	}
437	mCoreInitConfig(core, NULL);
438	core->init(core);
439	core->setAVStream(core, &stream);
440
441	outputBuffer = malloc(256 * 224 * BYTES_PER_PIXEL);
442	core->setVideoBuffer(core, outputBuffer, 256);
443
444	core->setAudioBufferSize(core, SAMPLES);
445
446	blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768);
447	blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768);
448
449	core->setPeripheral(core, mPERIPH_RUMBLE, &rumble);
450
451	savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
452	struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
453
454	_reloadSettings();
455	core->loadROM(core, rom);
456	core->loadSave(core, save);
457
458	const char* sysDir = 0;
459	const char* biosName = 0;
460	char biosPath[PATH_MAX];
461	environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir);
462
463#ifdef M_CORE_GBA
464	if (core->platform(core) == PLATFORM_GBA) {
465		core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &lux);
466		biosName = "gba_bios.bin";
467
468	}
469#endif
470
471#ifdef M_CORE_GB
472	if (core->platform(core) == PLATFORM_GB) {
473		const char* modelName = mCoreConfigGetValue(&core->config, "gb.model");
474		struct GB* gb = core->board;
475
476		if (modelName) {
477			gb->model = GBNameToModel(modelName);
478		} else {
479			GBDetectModel(gb);
480		}
481
482		switch (gb->model) {
483		case GB_MODEL_CGB:
484			biosName = "gbc_bios.bin";
485			break;
486		case GB_MODEL_SGB:
487			biosName = "sgb_bios.bin";
488			break;
489		case GB_MODEL_DMG:
490		default:
491			biosName = "gb_bios.bin";
492			break;
493		};
494	}
495#endif
496
497	if (core->opts.useBios && sysDir && biosName) {
498		snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, biosName);
499		struct VFile* bios = VFileOpen(biosPath, O_RDONLY);
500		if (bios) {
501			core->loadBIOS(core, bios, 0);
502		}
503	}
504
505	core->reset(core);
506	_setupMaps(core);
507
508	return true;
509}
510
511void retro_unload_game(void) {
512	if (!core) {
513		return;
514	}
515	core->deinit(core);
516	mappedMemoryFree(data, dataSize);
517	data = 0;
518	mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
519	savedata = 0;
520	CircleBufferDeinit(&rumbleHistory);
521}
522
523size_t retro_serialize_size(void) {
524	return core->stateSize(core);
525}
526
527bool retro_serialize(void* data, size_t size) {
528	if (size != retro_serialize_size()) {
529		return false;
530	}
531	core->saveState(core, data);
532	return true;
533}
534
535bool retro_unserialize(const void* data, size_t size) {
536	if (size != retro_serialize_size()) {
537		return false;
538	}
539	core->loadState(core, data);
540	return true;
541}
542
543void retro_cheat_reset(void) {
544	mCheatDeviceClear(core->cheatDevice(core));
545}
546
547void retro_cheat_set(unsigned index, bool enabled, const char* code) {
548	UNUSED(index);
549	UNUSED(enabled);
550	struct mCheatDevice* device = core->cheatDevice(core);
551	struct mCheatSet* cheatSet = NULL;
552	if (mCheatSetsSize(&device->cheats)) {
553		cheatSet = *mCheatSetsGetPointer(&device->cheats, 0);
554	} else {
555		cheatSet = device->createSet(device, NULL);
556		mCheatAddSet(device, cheatSet);
557	}
558	// Convert the super wonky unportable libretro format to something normal
559	char realCode[] = "XXXXXXXX XXXXXXXX";
560	size_t len = strlen(code) + 1; // Include null terminator
561	size_t i, pos;
562	for (i = 0, pos = 0; i < len; ++i) {
563		if (isspace((int) code[i]) || code[i] == '+') {
564			realCode[pos] = ' ';
565		} else {
566			realCode[pos] = code[i];
567		}
568		if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) {
569			realCode[pos] = '\0';
570			mCheatAddLine(cheatSet, realCode, 0);
571			pos = 0;
572			continue;
573		}
574		++pos;
575	}
576}
577
578unsigned retro_get_region(void) {
579	return RETRO_REGION_NTSC; // TODO: This isn't strictly true
580}
581
582void retro_set_controller_port_device(unsigned port, unsigned device) {
583	UNUSED(port);
584	UNUSED(device);
585}
586
587bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
588	UNUSED(game_type);
589	UNUSED(info);
590	UNUSED(num_info);
591	return false;
592}
593
594void* retro_get_memory_data(unsigned id) {
595	if (id != RETRO_MEMORY_SAVE_RAM) {
596		return 0;
597	}
598	return savedata;
599}
600
601size_t retro_get_memory_size(unsigned id) {
602	if (id != RETRO_MEMORY_SAVE_RAM) {
603		return 0;
604	}
605#ifdef M_CORE_GBA
606	if (core->platform(core) == PLATFORM_GBA) {
607		switch (((struct GBA*) core->board)->memory.savedata.type) {
608		case SAVEDATA_AUTODETECT:
609			return SIZE_CART_FLASH1M;
610		default:
611			return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
612		}
613	}
614#endif
615#ifdef M_CORE_GB
616	if (core->platform(core) == PLATFORM_GB) {
617		return ((struct GB*) core->board)->sramSize;
618	}
619#endif
620	return 0;
621}
622
623void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
624	UNUSED(logger);
625	if (!logCallback) {
626		return;
627	}
628
629	char message[128];
630	vsnprintf(message, sizeof(message), format, args);
631
632	enum retro_log_level retroLevel = RETRO_LOG_INFO;
633	switch (level) {
634	case mLOG_ERROR:
635	case mLOG_FATAL:
636		retroLevel = RETRO_LOG_ERROR;
637		break;
638	case mLOG_WARN:
639		retroLevel = RETRO_LOG_WARN;
640		break;
641	case mLOG_INFO:
642		retroLevel = RETRO_LOG_INFO;
643		break;
644	case mLOG_GAME_ERROR:
645	case mLOG_STUB:
646#ifdef NDEBUG
647		return;
648#else
649		retroLevel = RETRO_LOG_DEBUG;
650		break;
651#endif
652	case mLOG_DEBUG:
653		retroLevel = RETRO_LOG_DEBUG;
654		break;
655	}
656#ifdef NDEBUG
657	static int biosCat = -1;
658	if (biosCat < 0) {
659		biosCat = mLogCategoryById("gba.bios");
660	}
661
662	if (category == biosCat) {
663		return;
664	}
665#endif
666	logCallback(retroLevel, "%s: %s\n", mLogCategoryName(category), message);
667}
668
669static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
670	UNUSED(stream);
671	int16_t samples[SAMPLES * 2];
672	blip_read_samples(left, samples, SAMPLES, true);
673	blip_read_samples(right, samples + 1, SAMPLES, true);
674	audioCallback(samples, SAMPLES);
675}
676
677static void _setRumble(struct mRumble* rumble, int enable) {
678	UNUSED(rumble);
679	if (!rumbleCallback) {
680		return;
681	}
682	rumbleLevel += enable;
683	if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
684		int8_t oldLevel;
685		CircleBufferRead8(&rumbleHistory, &oldLevel);
686		rumbleLevel -= oldLevel;
687	}
688	CircleBufferWrite8(&rumbleHistory, enable);
689	rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
690	rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM);
691}
692
693static void _updateLux(struct GBALuminanceSource* lux) {
694	UNUSED(lux);
695	struct retro_variable var = {
696		.key = "mgba_solar_sensor_level",
697		.value = 0
698	};
699
700	bool updated = false;
701	if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
702		return;
703	}
704	if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
705		return;
706	}
707
708	char* end;
709	int newLuxLevel = strtol(var.value, &end, 10);
710	if (!*end) {
711		if (newLuxLevel > 10) {
712			luxLevel = 10;
713		} else if (newLuxLevel < 0) {
714			luxLevel = 0;
715		} else {
716			luxLevel = newLuxLevel;
717		}
718	}
719}
720
721static uint8_t _readLux(struct GBALuminanceSource* lux) {
722	UNUSED(lux);
723	int value = 0x16;
724	if (luxLevel > 0) {
725		value += GBA_LUX_LEVELS[luxLevel - 1];
726	}
727	return 0xFF - value;
728}