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