all repos — mgba @ ea2159d159f2ddb5514b9137a116eb74a2ed872f

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