all repos — mgba @ fc53fc9647353e3757a8538c01d6fd1e9a0ed3ca

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
323static void _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	size_t size = 256 * 224 * BYTES_PER_PIXEL;
442	outputBuffer = malloc(size);
443	memset(outputBuffer, 0xFF, size);
444	core->setVideoBuffer(core, outputBuffer, 256);
445
446	core->setAudioBufferSize(core, SAMPLES);
447
448	blip_set_rates(core->getAudioChannel(core, 0), core->frequency(core), 32768);
449	blip_set_rates(core->getAudioChannel(core, 1), core->frequency(core), 32768);
450
451	core->setPeripheral(core, mPERIPH_RUMBLE, &rumble);
452
453	savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
454	struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
455
456	_reloadSettings();
457	core->loadROM(core, rom);
458	core->loadSave(core, save);
459
460	const char* sysDir = 0;
461	const char* biosName = 0;
462	char biosPath[PATH_MAX];
463	environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir);
464
465#ifdef M_CORE_GBA
466	if (core->platform(core) == PLATFORM_GBA) {
467		core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &lux);
468		biosName = "gba_bios.bin";
469
470	}
471#endif
472
473#ifdef M_CORE_GB
474	if (core->platform(core) == PLATFORM_GB) {
475		const char* modelName = mCoreConfigGetValue(&core->config, "gb.model");
476		struct GB* gb = core->board;
477
478		if (modelName) {
479			gb->model = GBNameToModel(modelName);
480		} else {
481			GBDetectModel(gb);
482		}
483
484		switch (gb->model) {
485		case GB_MODEL_CGB:
486			biosName = "gbc_bios.bin";
487			break;
488		case GB_MODEL_SGB:
489			biosName = "sgb_bios.bin";
490			break;
491		case GB_MODEL_DMG:
492		default:
493			biosName = "gb_bios.bin";
494			break;
495		};
496	}
497#endif
498
499	if (core->opts.useBios && sysDir && biosName) {
500		snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, biosName);
501		struct VFile* bios = VFileOpen(biosPath, O_RDONLY);
502		if (bios) {
503			core->loadBIOS(core, bios, 0);
504		}
505	}
506
507	core->reset(core);
508	_setupMaps(core);
509
510	return true;
511}
512
513void retro_unload_game(void) {
514	if (!core) {
515		return;
516	}
517	core->deinit(core);
518	mappedMemoryFree(data, dataSize);
519	data = 0;
520	mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
521	savedata = 0;
522	CircleBufferDeinit(&rumbleHistory);
523}
524
525size_t retro_serialize_size(void) {
526	return core->stateSize(core);
527}
528
529bool retro_serialize(void* data, size_t size) {
530	if (size != retro_serialize_size()) {
531		return false;
532	}
533	core->saveState(core, data);
534	return true;
535}
536
537bool retro_unserialize(const void* data, size_t size) {
538	if (size != retro_serialize_size()) {
539		return false;
540	}
541	core->loadState(core, data);
542	return true;
543}
544
545void retro_cheat_reset(void) {
546	mCheatDeviceClear(core->cheatDevice(core));
547}
548
549void retro_cheat_set(unsigned index, bool enabled, const char* code) {
550	UNUSED(index);
551	UNUSED(enabled);
552	struct mCheatDevice* device = core->cheatDevice(core);
553	struct mCheatSet* cheatSet = NULL;
554	if (mCheatSetsSize(&device->cheats)) {
555		cheatSet = *mCheatSetsGetPointer(&device->cheats, 0);
556	} else {
557		cheatSet = device->createSet(device, NULL);
558		mCheatAddSet(device, cheatSet);
559	}
560	// Convert the super wonky unportable libretro format to something normal
561	char realCode[] = "XXXXXXXX XXXXXXXX";
562	size_t len = strlen(code) + 1; // Include null terminator
563	size_t i, pos;
564	for (i = 0, pos = 0; i < len; ++i) {
565		if (isspace((int) code[i]) || code[i] == '+') {
566			realCode[pos] = ' ';
567		} else {
568			realCode[pos] = code[i];
569		}
570		if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) {
571			realCode[pos] = '\0';
572			mCheatAddLine(cheatSet, realCode, 0);
573			pos = 0;
574			continue;
575		}
576		++pos;
577	}
578}
579
580unsigned retro_get_region(void) {
581	return RETRO_REGION_NTSC; // TODO: This isn't strictly true
582}
583
584void retro_set_controller_port_device(unsigned port, unsigned device) {
585	UNUSED(port);
586	UNUSED(device);
587}
588
589bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
590	UNUSED(game_type);
591	UNUSED(info);
592	UNUSED(num_info);
593	return false;
594}
595
596void* retro_get_memory_data(unsigned id) {
597	if (id != RETRO_MEMORY_SAVE_RAM) {
598		return 0;
599	}
600	return savedata;
601}
602
603size_t retro_get_memory_size(unsigned id) {
604	if (id != RETRO_MEMORY_SAVE_RAM) {
605		return 0;
606	}
607#ifdef M_CORE_GBA
608	if (core->platform(core) == PLATFORM_GBA) {
609		switch (((struct GBA*) core->board)->memory.savedata.type) {
610		case SAVEDATA_AUTODETECT:
611			return SIZE_CART_FLASH1M;
612		default:
613			return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
614		}
615	}
616#endif
617#ifdef M_CORE_GB
618	if (core->platform(core) == PLATFORM_GB) {
619		return ((struct GB*) core->board)->sramSize;
620	}
621#endif
622	return 0;
623}
624
625void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
626	UNUSED(logger);
627	if (!logCallback) {
628		return;
629	}
630
631	char message[128];
632	vsnprintf(message, sizeof(message), format, args);
633
634	enum retro_log_level retroLevel = RETRO_LOG_INFO;
635	switch (level) {
636	case mLOG_ERROR:
637	case mLOG_FATAL:
638		retroLevel = RETRO_LOG_ERROR;
639		break;
640	case mLOG_WARN:
641		retroLevel = RETRO_LOG_WARN;
642		break;
643	case mLOG_INFO:
644		retroLevel = RETRO_LOG_INFO;
645		break;
646	case mLOG_GAME_ERROR:
647	case mLOG_STUB:
648#ifdef NDEBUG
649		return;
650#else
651		retroLevel = RETRO_LOG_DEBUG;
652		break;
653#endif
654	case mLOG_DEBUG:
655		retroLevel = RETRO_LOG_DEBUG;
656		break;
657	}
658#ifdef NDEBUG
659	static int biosCat = -1;
660	if (biosCat < 0) {
661		biosCat = mLogCategoryById("gba.bios");
662	}
663
664	if (category == biosCat) {
665		return;
666	}
667#endif
668	logCallback(retroLevel, "%s: %s\n", mLogCategoryName(category), message);
669}
670
671static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right) {
672	UNUSED(stream);
673	int16_t samples[SAMPLES * 2];
674	blip_read_samples(left, samples, SAMPLES, true);
675	blip_read_samples(right, samples + 1, SAMPLES, true);
676	audioCallback(samples, SAMPLES);
677}
678
679static void _setRumble(struct mRumble* rumble, int enable) {
680	UNUSED(rumble);
681	if (!rumbleCallback) {
682		return;
683	}
684	rumbleLevel += enable;
685	if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
686		int8_t oldLevel;
687		CircleBufferRead8(&rumbleHistory, &oldLevel);
688		rumbleLevel -= oldLevel;
689	}
690	CircleBufferWrite8(&rumbleHistory, enable);
691	rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
692	rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM);
693}
694
695static void _updateLux(struct GBALuminanceSource* lux) {
696	UNUSED(lux);
697	struct retro_variable var = {
698		.key = "mgba_solar_sensor_level",
699		.value = 0
700	};
701
702	bool updated = false;
703	if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
704		return;
705	}
706	if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
707		return;
708	}
709
710	char* end;
711	int newLuxLevel = strtol(var.value, &end, 10);
712	if (!*end) {
713		if (newLuxLevel > 10) {
714			luxLevel = 10;
715		} else if (newLuxLevel < 0) {
716			luxLevel = 0;
717		} else {
718			luxLevel = newLuxLevel;
719		}
720	}
721}
722
723static uint8_t _readLux(struct GBALuminanceSource* lux) {
724	UNUSED(lux);
725	int value = 0x16;
726	if (luxLevel > 0) {
727		value += GBA_LUX_LEVELS[luxLevel - 1];
728	}
729	return 0xFF - value;
730}