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 "util/common.h"
9
10#include "gba/cheats.h"
11#include "gba/renderers/video-software.h"
12#include "gba/serialize.h"
13#include "gba/context/context.h"
14#include "util/circle-buffer.h"
15#include "util/memory.h"
16#include "util/vfs.h"
17
18#define SAMPLES 1024
19#define RUMBLE_PWM 35
20
21#define SOLAR_SENSOR_LEVEL "mgba_solar_sensor_level"
22#define ALLOW_OPPOSING_DIRECTIONS "mgba_allow_opposing_directions"
23
24static retro_environment_t environCallback;
25static retro_video_refresh_t videoCallback;
26static retro_audio_sample_batch_t audioCallback;
27static retro_input_poll_t inputPollCallback;
28static retro_input_state_t inputCallback;
29static retro_log_printf_t logCallback;
30static retro_set_rumble_state_t rumbleCallback;
31
32static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
33
34static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
35static void _setRumble(struct GBARumble* rumble, int enable);
36static uint8_t _readLux(struct GBALuminanceSource* lux);
37static void _updateLux(struct GBALuminanceSource* lux);
38
39static struct GBAContext context;
40static struct GBAVideoSoftwareRenderer renderer;
41static void* data;
42static size_t dataSize;
43static void* savedata;
44static struct GBAAVStream stream;
45static int rumbleLevel;
46static struct CircleBuffer rumbleHistory;
47static struct GBARumble rumble;
48static struct GBALuminanceSource lux;
49static int luxLevel;
50static struct GBACheatDevice cheats;
51static struct GBACheatSet cheatSet;
52
53unsigned retro_api_version(void) {
54 return RETRO_API_VERSION;
55}
56
57void retro_set_environment(retro_environment_t env) {
58 environCallback = env;
59
60 struct retro_variable vars[] = {
61 { SOLAR_SENSOR_LEVEL, "Solar sensor level; 0|1|2|3|4|5|6|7|8|9|10" },
62 { ALLOW_OPPOSING_DIRECTIONS, "Allow opposing directional input; no|yes" },
63 { 0, 0 }
64 };
65
66 environCallback(RETRO_ENVIRONMENT_SET_VARIABLES, vars);
67}
68
69void retro_set_video_refresh(retro_video_refresh_t video) {
70 videoCallback = video;
71}
72
73void retro_set_audio_sample(retro_audio_sample_t audio) {
74 UNUSED(audio);
75}
76
77void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
78 audioCallback = audioBatch;
79}
80
81void retro_set_input_poll(retro_input_poll_t inputPoll) {
82 inputPollCallback = inputPoll;
83}
84
85void retro_set_input_state(retro_input_state_t input) {
86 inputCallback = input;
87}
88
89void retro_get_system_info(struct retro_system_info* info) {
90 info->need_fullpath = false;
91 info->valid_extensions = "gba";
92 info->library_version = projectVersion;
93 info->library_name = projectName;
94 info->block_extract = false;
95}
96
97void retro_get_system_av_info(struct retro_system_av_info* info) {
98 info->geometry.base_width = VIDEO_HORIZONTAL_PIXELS;
99 info->geometry.base_height = VIDEO_VERTICAL_PIXELS;
100 info->geometry.max_width = VIDEO_HORIZONTAL_PIXELS;
101 info->geometry.max_height = VIDEO_VERTICAL_PIXELS;
102 info->timing.fps = GBA_ARM7TDMI_FREQUENCY / (float) VIDEO_TOTAL_LENGTH;
103 info->timing.sample_rate = 32768;
104}
105
106void retro_init(void) {
107 enum retro_pixel_format fmt;
108#ifdef COLOR_16_BIT
109#ifdef COLOR_5_6_5
110 fmt = RETRO_PIXEL_FORMAT_RGB565;
111#else
112#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
113 fmt = RETRO_PIXEL_FORMAT_0RGB1555;
114#endif
115#else
116#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
117 fmt = RETRO_PIXEL_FORMAT_XRGB8888;
118#endif
119 environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
120
121 struct retro_input_descriptor inputDescriptors[] = {
122 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
123 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
124 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
125 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
126 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
127 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
128 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
129 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
130 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
131 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" },
132 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "Brighten Solar Sensor" },
133 { 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "Darken Solar Sensor" }
134 };
135 environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
136
137 // TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
138
139 struct retro_rumble_interface rumbleInterface;
140 if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
141 rumbleCallback = rumbleInterface.set_rumble_state;
142 CircleBufferInit(&rumbleHistory, RUMBLE_PWM);
143 rumble.setRumble = _setRumble;
144 } else {
145 rumbleCallback = 0;
146 }
147
148 luxLevel = 0;
149 lux.readLuminance = _readLux;
150 lux.sample = _updateLux;
151 _updateLux(&lux);
152
153 struct retro_log_callback log;
154 if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
155 logCallback = log.log;
156 } else {
157 logCallback = 0;
158 }
159
160 stream.postAudioFrame = 0;
161 stream.postAudioBuffer = _postAudioBuffer;
162 stream.postVideoFrame = 0;
163
164 GBAContextInit(&context, 0);
165 struct GBAOptions opts = {
166 .useBios = true,
167 .idleOptimization = IDLE_LOOP_REMOVE
168 };
169 GBAConfigLoadDefaults(&context.config, &opts);
170 context.gba->logHandler = GBARetroLog;
171 context.gba->stream = &stream;
172 if (rumbleCallback) {
173 context.gba->rumble = &rumble;
174 }
175 context.gba->luminanceSource = &lux;
176
177 const char* sysDir = 0;
178 if (environCallback(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY, &sysDir)) {
179 char biosPath[PATH_MAX];
180 snprintf(biosPath, sizeof(biosPath), "%s%s%s", sysDir, PATH_SEP, "gba_bios.bin");
181 struct VFile* bios = VFileOpen(biosPath, O_RDONLY);
182 if (bios) {
183 GBAContextLoadBIOSFromVFile(&context, bios);
184 }
185 }
186
187 GBAVideoSoftwareRendererCreate(&renderer);
188 renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
189 renderer.outputBufferStride = 256;
190 context.renderer = &renderer.d;
191
192 GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);
193
194#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
195 blip_set_rates(context.gba->audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
196 blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
197#endif
198
199 GBACheatDeviceCreate(&cheats);
200 GBACheatAttachDevice(context.gba, &cheats);
201 GBACheatSetInit(&cheatSet, "libretro");
202 GBACheatAddSet(&cheats, &cheatSet);
203}
204
205void retro_deinit(void) {
206 GBAContextDeinit(&context);
207 GBACheatRemoveSet(&cheats, &cheatSet);
208 GBACheatDeviceDestroy(&cheats);
209 GBACheatSetDeinit(&cheatSet);
210 free(renderer.outputBuffer);
211}
212
213void retro_run(void) {
214 uint16_t keys;
215 inputPollCallback();
216
217 struct retro_variable var = {
218 .key = ALLOW_OPPOSING_DIRECTIONS,
219 .value = 0
220 };
221
222 bool updated = false;
223 if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
224 if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {
225 context.gba->allowOpposingDirections = strcmp(var.value, "yes") == 0;
226 }
227 }
228
229 keys = 0;
230 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
231 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
232 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2;
233 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3;
234 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4;
235 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5;
236 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6;
237 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;
238 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
239 keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
240
241 static bool wasAdjustingLux = false;
242 if (wasAdjustingLux) {
243 wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
244 inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
245 } else {
246 if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
247 ++luxLevel;
248 if (luxLevel > 10) {
249 luxLevel = 10;
250 }
251 wasAdjustingLux = true;
252 } else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
253 --luxLevel;
254 if (luxLevel < 0) {
255 luxLevel = 0;
256 }
257 wasAdjustingLux = true;
258 }
259 }
260
261 GBAContextFrame(&context, keys);
262 videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * renderer.outputBufferStride);
263}
264
265void retro_reset(void) {
266 ARMReset(context.cpu);
267
268 if (rumbleCallback) {
269 CircleBufferClear(&rumbleHistory);
270 }
271}
272
273bool retro_load_game(const struct retro_game_info* game) {
274 struct VFile* rom;
275 if (game->data) {
276 data = anonymousMemoryMap(game->size);
277 dataSize = game->size;
278 memcpy(data, game->data, game->size);
279 rom = VFileFromMemory(data, game->size);
280 } else {
281 data = 0;
282 rom = VFileOpen(game->path, O_RDONLY);
283 }
284 if (!rom) {
285 return false;
286 }
287 if (!GBAIsROM(rom)) {
288 rom->close(rom);
289 mappedMemoryFree(data, game->size);
290 return false;
291 }
292
293 savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
294 struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
295
296 GBAContextLoadROMFromVFile(&context, rom, save);
297 GBAContextStart(&context);
298 return true;
299}
300
301void retro_unload_game(void) {
302 GBAContextStop(&context);
303 mappedMemoryFree(data, dataSize);
304 data = 0;
305 mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
306 savedata = 0;
307 CircleBufferDeinit(&rumbleHistory);
308}
309
310size_t retro_serialize_size(void) {
311 return sizeof(struct GBASerializedState);
312}
313
314bool retro_serialize(void* data, size_t size) {
315 if (size != retro_serialize_size()) {
316 return false;
317 }
318 GBASerialize(context.gba, data);
319 return true;
320}
321
322bool retro_unserialize(const void* data, size_t size) {
323 if (size != retro_serialize_size()) {
324 return false;
325 }
326 GBADeserialize(context.gba, data);
327 return true;
328}
329
330void retro_cheat_reset(void) {
331 GBACheatSetDeinit(&cheatSet);
332 GBACheatSetInit(&cheatSet, "libretro");
333}
334
335void retro_cheat_set(unsigned index, bool enabled, const char* code) {
336 UNUSED(index);
337 UNUSED(enabled);
338 // Convert the super wonky unportable libretro format to something normal
339 char realCode[] = "XXXXXXXX XXXXXXXX";
340 size_t len = strlen(code) + 1; // Include null terminator
341 size_t i, pos;
342 for (i = 0, pos = 0; i < len; ++i) {
343 if (isspace((int) code[i]) || code[i] == '+') {
344 realCode[pos] = ' ';
345 } else {
346 realCode[pos] = code[i];
347 }
348 if ((pos == 13 && (realCode[pos] == ' ' || !realCode[pos])) || pos == 17) {
349 realCode[pos] = '\0';
350 GBACheatAddLine(&cheatSet, realCode);
351 pos = 0;
352 continue;
353 }
354 ++pos;
355 }
356}
357
358unsigned retro_get_region(void) {
359 return RETRO_REGION_NTSC; // TODO: This isn't strictly true
360}
361
362void retro_set_controller_port_device(unsigned port, unsigned device) {
363 UNUSED(port);
364 UNUSED(device);
365}
366
367bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
368 UNUSED(game_type);
369 UNUSED(info);
370 UNUSED(num_info);
371 return false;
372}
373
374void* retro_get_memory_data(unsigned id) {
375 if (id != RETRO_MEMORY_SAVE_RAM) {
376 return 0;
377 }
378 return savedata;
379}
380
381size_t retro_get_memory_size(unsigned id) {
382 if (id != RETRO_MEMORY_SAVE_RAM) {
383 return 0;
384 }
385 switch (context.gba->memory.savedata.type) {
386 case SAVEDATA_AUTODETECT:
387 case SAVEDATA_FLASH1M:
388 return SIZE_CART_FLASH1M;
389 case SAVEDATA_FLASH512:
390 return SIZE_CART_FLASH512;
391 case SAVEDATA_EEPROM:
392 return SIZE_CART_EEPROM;
393 case SAVEDATA_SRAM:
394 return SIZE_CART_SRAM;
395 case SAVEDATA_FORCE_NONE:
396 return 0;
397 }
398 return 0;
399}
400
401void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
402 UNUSED(thread);
403 if (!logCallback) {
404 return;
405 }
406
407 char message[128];
408 vsnprintf(message, sizeof(message), format, args);
409
410 enum retro_log_level retroLevel = RETRO_LOG_INFO;
411 switch (level) {
412 case GBA_LOG_ALL:
413 case GBA_LOG_ERROR:
414 case GBA_LOG_FATAL:
415 retroLevel = RETRO_LOG_ERROR;
416 break;
417 case GBA_LOG_WARN:
418 retroLevel = RETRO_LOG_WARN;
419 break;
420 case GBA_LOG_INFO:
421 case GBA_LOG_GAME_ERROR:
422 case GBA_LOG_SWI:
423 case GBA_LOG_STATUS:
424 retroLevel = RETRO_LOG_INFO;
425 break;
426 case GBA_LOG_DEBUG:
427 case GBA_LOG_STUB:
428 case GBA_LOG_SIO:
429 retroLevel = RETRO_LOG_DEBUG;
430 break;
431 }
432 logCallback(retroLevel, "%s\n", message);
433}
434
435static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) {
436 UNUSED(stream);
437 int16_t samples[SAMPLES * 2];
438#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
439 blip_read_samples(audio->left, samples, SAMPLES, true);
440 blip_read_samples(audio->right, samples + 1, SAMPLES, true);
441#else
442 int16_t samplesR[SAMPLES];
443 GBAAudioCopy(audio, &samples[SAMPLES], samplesR, SAMPLES);
444 size_t i;
445 for (i = 0; i < SAMPLES; ++i) {
446 samples[i * 2] = samples[SAMPLES + i];
447 samples[i * 2 + 1] = samplesR[i];
448 }
449#endif
450 audioCallback(samples, SAMPLES);
451}
452
453static void _setRumble(struct GBARumble* rumble, int enable) {
454 UNUSED(rumble);
455 if (!rumbleCallback) {
456 return;
457 }
458 rumbleLevel += enable;
459 if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
460 int8_t oldLevel;
461 CircleBufferRead8(&rumbleHistory, &oldLevel);
462 rumbleLevel -= oldLevel;
463 }
464 CircleBufferWrite8(&rumbleHistory, enable);
465 rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
466 rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM);
467}
468
469static void _updateLux(struct GBALuminanceSource* lux) {
470 UNUSED(lux);
471 struct retro_variable var = {
472 .key = SOLAR_SENSOR_LEVEL,
473 .value = 0
474 };
475
476 bool updated = false;
477 if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
478 return;
479 }
480 if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
481 return;
482 }
483
484 char* end;
485 int newLuxLevel = strtol(var.value, &end, 10);
486 if (!*end) {
487 if (newLuxLevel > 10) {
488 luxLevel = 10;
489 } else if (newLuxLevel < 0) {
490 luxLevel = 0;
491 } else {
492 luxLevel = newLuxLevel;
493 }
494 }
495}
496
497static uint8_t _readLux(struct GBALuminanceSource* lux) {
498 UNUSED(lux);
499 int value = 0x16;
500 if (luxLevel > 0) {
501 value += GBA_LUX_LEVELS[luxLevel - 1];
502 }
503 return 0xFF - value;
504}