7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_ENV_IMPL_ONCE)
8#define SDL_LIBRETRO_ENV_IMPL_ONCE
16static void SDL_Libretro_Logger(
enum retro_log_level level,
const char* fmt, ...) {
17 if (SDL_Libretro_active && level < SDL_Libretro_active->logLevel) {
24 SDL_vsnprintf(buf,
sizeof(buf), fmt, args);
28 size_t len = SDL_strlen(buf);
29 if (len > 0 && buf[len - 1] ==
'\n') buf[len - 1] =
'\0';
32 case RETRO_LOG_INFO: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] %s", buf);
break;
33 case RETRO_LOG_WARN: SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] %s", buf);
break;
34 case RETRO_LOG_ERROR: SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] %s", buf);
break;
36 default: SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] %s", buf);
break;
40static retro_time_t SDL_Libretro_GetTimeUSEC(
void) {
42 return (retro_time_t)(SDL_GetTicksNS() / SDL_NS_PER_US);
45static uint64_t SDL_Libretro_GetCPUFeatures(
void) {
46 uint64_t features = 0;
47 if (SDL_HasSSE()) features |= RETRO_SIMD_SSE;
48 if (SDL_HasSSE2()) features |= RETRO_SIMD_SSE2;
49 if (SDL_HasSSE3()) features |= RETRO_SIMD_SSE3;
50 if (SDL_HasSSE41()) features |= RETRO_SIMD_SSE4;
51 if (SDL_HasSSE42()) features |= RETRO_SIMD_SSE42;
52 if (SDL_HasAVX()) features |= RETRO_SIMD_AVX;
53 if (SDL_HasAVX2()) features |= RETRO_SIMD_AVX2;
54 if (SDL_HasAVX512F()) features |= RETRO_SIMD_AVX512;
55 if (SDL_HasMMX()) features |= RETRO_SIMD_MMX;
56 if (SDL_HasAltiVec()) features |= RETRO_SIMD_VMX;
58 features |= RETRO_SIMD_NEON;
59#if defined(__aarch64__) || defined(_M_ARM64)
61 features |= RETRO_SIMD_ASIMD;
67static retro_perf_tick_t SDL_Libretro_GetPerfCounter(
void) {
68 return (retro_perf_tick_t)SDL_GetPerformanceCounter();
71static void SDL_Libretro_PerfRegister(
struct retro_perf_counter* counter) {
73 if (!lr || !counter)
return;
76 for (
unsigned i = 0; i < lr->core.perfCounterCount; i++) {
77 if (lr->core.perfCounters[i] == counter) {
78 counter->registered =
true;
83 if (lr->core.perfCounterCount >= SDL_arraysize(lr->core.perfCounters)) {
84 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"PERF: counter registry full (%u), dropping '%s'", lr->core.perfCounterCount, counter->ident ? counter->ident :
"?");
88 lr->core.perfCounters[lr->core.perfCounterCount++] = counter;
89 counter->registered =
true;
92static void SDL_Libretro_PerfStart(
struct retro_perf_counter* counter) {
95 counter->start = (retro_perf_tick_t)SDL_GetPerformanceCounter();
98static void SDL_Libretro_PerfStop(
struct retro_perf_counter* counter) {
99 if (counter) counter->total += (retro_perf_tick_t)SDL_GetPerformanceCounter() - counter->start;
106static void SDL_Libretro_PerfLog(
void) {
111 Uint64 freq = SDL_GetPerformanceFrequency();
113 for (
unsigned i = 0; i < lr->core.perfCounterCount; i++) {
114 const struct retro_perf_counter* counter = lr->core.perfCounters[i];
115 if (!counter)
continue;
117 double ms = freq ? ((double)counter->total / (
double)freq) * 1000.0 : 0.0;
118 SDL_Log(
"[PERF] %s: %llu calls, %llu ticks (%.4f ms)", counter->ident ? counter->ident :
"?", (unsigned long long)counter->call_cnt, (unsigned long long)counter->total, ms);
128static void SDL_Libretro_ApplyRumble(SDL_Libretro* lr,
unsigned port) {
131 Uint16 lo = (Uint16)(lr->core.rumbleStrong[port] * 65535.0f);
132 Uint16 hi = (Uint16)(lr->core.rumbleWeak[port] * 65535.0f);
133 SDL_RumbleGamepad(lr->gamepads[port], lo, hi, (lo || hi) ? 1000 : 0);
140static void SDL_Libretro_StopRumble(SDL_Libretro* lr) {
143 bool active = lr->core.rumbleStrong[port] > 0.0f || lr->core.rumbleWeak[port] > 0.0f;
144 lr->core.rumbleStrong[port] = 0.0f;
145 lr->core.rumbleWeak[port] = 0.0f;
146 if (active) SDL_Libretro_ApplyRumble(lr, port);
150static bool SDL_Libretro_SetRumbleState(
unsigned port,
enum retro_rumble_effect effect, uint16_t strength) {
154 float normalized = (float)strength / 65535.0f;
155 if (effect == RETRO_RUMBLE_STRONG) {
156 lr->core.rumbleStrong[port] = normalized;
159 lr->core.rumbleWeak[port] = normalized;
162 SDL_Libretro_ApplyRumble(lr, port);
167static void SDL_Libretro_SetLEDState(
int led,
int state) {
170 Uint8 v = state ? 255 : 0;
172 if (lr->gamepads[led]) SDL_SetGamepadLED(lr->gamepads[led], v, v, v);
176 if (lr->gamepads[i]) SDL_SetGamepadLED(lr->gamepads[i], v, v, v);
181static const char* SDL_Libretro_GetDirectory(SDL_Libretro* lr,
unsigned cmd) {
183 case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
184 return lr->systemDirectory[0] ? lr->systemDirectory : NULL;
185 case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
186 return lr->saveDirectory[0] ? lr->saveDirectory : NULL;
187 case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
188 return lr->coreAssetsDirectory[0] ? lr->coreAssetsDirectory : NULL;
189 case RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY:
190 return lr->playlistDirectory[0] ? lr->playlistDirectory : NULL;
191 case RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY:
192 return lr->fileBrowserStartDirectory[0] ? lr->fileBrowserStartDirectory : NULL;
203static float SDL_Libretro_GetTargetRefreshRate(SDL_Libretro* lr) {
206 SDL_DisplayID display = SDL_GetDisplayForWindow(lr->window);
208 const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
209 if (mode && mode->refresh_rate > 0.0f) {
210 rate = mode->refresh_rate;
223static const char* SDL_Libretro_PickCoreOptionText(
const char* base,
const char* categorized,
const char* categoryKey) {
224 if (categoryKey && categoryKey[0] && categorized && categorized[0]) {
237static int SDL_Libretro_LanguageFromLocale(
const char* language,
const char* country) {
238 if (!language || !language[0])
return RETRO_LANGUAGE_ENGLISH;
239 if (!country) country =
"";
242 if (SDL_strcasecmp(language,
"ca") == 0) {
243 bool valencia = SDL_strcasestr(country,
"valencia") != NULL || SDL_strcasestr(language,
"valencia") != NULL;
244 return valencia ? RETRO_LANGUAGE_CATALAN_VALENCIA : RETRO_LANGUAGE_CATALAN;
246 if (SDL_strcasecmp(language,
"en") == 0) {
247 return SDL_strcasecmp(country,
"GB") == 0 ? RETRO_LANGUAGE_BRITISH_ENGLISH : RETRO_LANGUAGE_ENGLISH;
249 if (SDL_strcasecmp(language,
"pt") == 0) {
250 return SDL_strcasecmp(country,
"PT") == 0 ? RETRO_LANGUAGE_PORTUGUESE_PORTUGAL : RETRO_LANGUAGE_PORTUGUESE_BRAZIL;
252 if (SDL_strcasecmp(language,
"zh") == 0) {
253 bool traditional = SDL_strcasecmp(country,
"TW") == 0 || SDL_strcasecmp(country,
"HK") == 0 || SDL_strcasecmp(country,
"MO") == 0;
254 return traditional ? RETRO_LANGUAGE_CHINESE_TRADITIONAL : RETRO_LANGUAGE_CHINESE_SIMPLIFIED;
257 static const struct {
261 {
"ja", RETRO_LANGUAGE_JAPANESE},
262 {
"fr", RETRO_LANGUAGE_FRENCH},
263 {
"es", RETRO_LANGUAGE_SPANISH},
264 {
"de", RETRO_LANGUAGE_GERMAN},
265 {
"it", RETRO_LANGUAGE_ITALIAN},
266 {
"nl", RETRO_LANGUAGE_DUTCH},
267 {
"ru", RETRO_LANGUAGE_RUSSIAN},
268 {
"ko", RETRO_LANGUAGE_KOREAN},
269 {
"eo", RETRO_LANGUAGE_ESPERANTO},
270 {
"pl", RETRO_LANGUAGE_POLISH},
271 {
"vi", RETRO_LANGUAGE_VIETNAMESE},
272 {
"ar", RETRO_LANGUAGE_ARABIC},
273 {
"el", RETRO_LANGUAGE_GREEK},
274 {
"tr", RETRO_LANGUAGE_TURKISH},
275 {
"sk", RETRO_LANGUAGE_SLOVAK},
276 {
"fa", RETRO_LANGUAGE_PERSIAN},
277 {
"he", RETRO_LANGUAGE_HEBREW},
278 {
"iw", RETRO_LANGUAGE_HEBREW},
279 {
"ast", RETRO_LANGUAGE_ASTURIAN},
280 {
"fi", RETRO_LANGUAGE_FINNISH},
281 {
"id", RETRO_LANGUAGE_INDONESIAN},
282 {
"in", RETRO_LANGUAGE_INDONESIAN},
283 {
"sv", RETRO_LANGUAGE_SWEDISH},
284 {
"uk", RETRO_LANGUAGE_UKRAINIAN},
285 {
"cs", RETRO_LANGUAGE_CZECH},
286 {
"hu", RETRO_LANGUAGE_HUNGARIAN},
287 {
"be", RETRO_LANGUAGE_BELARUSIAN},
288 {
"gl", RETRO_LANGUAGE_GALICIAN},
289 {
"no", RETRO_LANGUAGE_NORWEGIAN},
290 {
"nb", RETRO_LANGUAGE_NORWEGIAN},
291 {
"nn", RETRO_LANGUAGE_NORWEGIAN},
292 {
"ga", RETRO_LANGUAGE_IRISH},
293 {
"th", RETRO_LANGUAGE_THAI},
295 for (
size_t i = 0; i < SDL_arraysize(languages); i++) {
296 if (SDL_strcasecmp(language, languages[i].code) == 0) {
297 return languages[i].language;
300 return RETRO_LANGUAGE_ENGLISH;
307static int SDL_Libretro_DetectLanguage(
void) {
309 SDL_Locale** locales = SDL_GetPreferredLocales(&count);
310 int language = RETRO_LANGUAGE_ENGLISH;
311 if (locales && count > 0 && locales[0]) {
312 language = SDL_Libretro_LanguageFromLocale(locales[0]->language, locales[0]->country);
320 lr->language = (language >= 0 && language < RETRO_LANGUAGE_LAST) ? language : -1;
324 if (!lr)
return RETRO_LANGUAGE_ENGLISH;
325 return lr->language >= 0 ? lr->language : SDL_Libretro_DetectLanguage();
332static const char* SDL_Libretro_PickLocalText(
const char* us,
const char* local) {
333 return (local && local[0]) ? local : us;
340static struct retro_core_option_v2_definition* SDL_Libretro_CoreOptionsV1ToV2(
const struct retro_core_option_definition* defs) {
342 while (defs[count].key) count++;
343 struct retro_core_option_v2_definition* v2defs =
344 (
struct retro_core_option_v2_definition*)SDL_calloc(count + 1,
sizeof(*v2defs));
345 if (!v2defs)
return NULL;
346 for (
unsigned i = 0; i < count; i++) {
347 v2defs[i].key = defs[i].key;
348 v2defs[i].desc = defs[i].desc;
349 v2defs[i].info = defs[i].info;
350 v2defs[i].default_value = defs[i].default_value;
351 SDL_memcpy(v2defs[i].values, defs[i].values,
sizeof(defs[i].values));
356static bool SDL_Libretro_EnvironmentCallback(
unsigned cmd,
void* data) {
358 if (!lr)
return false;
361 case RETRO_ENVIRONMENT_SET_ROTATION: {
362 if (!data)
return false;
364 lr->core.rotation = (int)(*(
const unsigned*)data & 3);
365 SDL_Log(
"[SDL_Libretro] SET_ROTATION: %d (%d deg)", lr->core.rotation, lr->core.rotation * 90);
369 case RETRO_ENVIRONMENT_GET_OVERSCAN: {
373 case RETRO_ENVIRONMENT_GET_CAN_DUPE: {
374 if (!data)
return false;
379 case RETRO_ENVIRONMENT_SET_MESSAGE: {
380 const struct retro_message* msg = (
const struct retro_message*)data;
381 if (!msg || !msg->msg || msg->msg[0] ==
'\0')
return msg != NULL;
382 double seconds = msg->frames / (lr->core.fps > 0 ? lr->core.fps : 60.0);
387 case RETRO_ENVIRONMENT_SHUTDOWN: {
388 SDL_Log(
"[SDL_Libretro] Shutdown requested");
389 lr->core.shutdown =
true;
394 case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL: {
395 if (!data)
return false;
396 lr->core.performanceLevel = *(
const unsigned*)data;
400 case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
401 case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
402 case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
403 case RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY:
404 case RETRO_ENVIRONMENT_GET_FILE_BROWSER_START_DIRECTORY: {
406 if (!data)
return true;
407 *(
const char**)data = SDL_Libretro_GetDirectory(lr, cmd);
411 case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: {
412 if (!data)
return false;
413 enum retro_pixel_format fmt = *(
const enum retro_pixel_format*)data;
415 case RETRO_PIXEL_FORMAT_0RGB1555:
416 case RETRO_PIXEL_FORMAT_XRGB8888:
417 case RETRO_PIXEL_FORMAT_RGB565: {
418 bool changed = (fmt != lr->core.pixelFormat);
419 lr->core.pixelFormat = fmt;
421 if (changed && lr->core.texture) {
422 lr->core.videoReinitPending =
true;
432 case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS: {
433 const struct retro_input_descriptor* desc = (
const struct retro_input_descriptor*)data;
434 if (!desc)
return true;
437 SDL_Libretro_FreeInputDescriptors(lr);
441 for (
const struct retro_input_descriptor* d = desc; d->description; d++) count++;
447 lr->core.inputDescriptors = (
struct retro_input_descriptor*)SDL_malloc(count *
sizeof(*desc));
448 if (!lr->core.inputDescriptors) {
449 return SDL_SetError(
"[SDL_Libretro] Out of memory copying input descriptors");
451 SDL_memcpy(lr->core.inputDescriptors, desc, count *
sizeof(*desc));
452 lr->core.inputDescriptorsCount = count;
454 for (
unsigned i = 0; i < count; i++) {
455 lr->core.inputDescriptors[i].description = SDL_strdup(desc[i].description);
461 case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK: {
462 if (!data)
return true;
463 const struct retro_keyboard_callback* cb = (
const struct retro_keyboard_callback*)data;
464 lr->core.keyboard_event = cb->callback;
468 case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE: {
469 const struct retro_disk_control_callback* cb = (
const struct retro_disk_control_callback*)data;
470 if (!cb)
return true;
471 SDL_memset(&lr->core.disk_control, 0,
sizeof(lr->core.disk_control));
472 lr->core.disk_control.set_eject_state = cb->set_eject_state;
473 lr->core.disk_control.get_eject_state = cb->get_eject_state;
474 lr->core.disk_control.get_image_index = cb->get_image_index;
475 lr->core.disk_control.set_image_index = cb->set_image_index;
476 lr->core.disk_control.get_num_images = cb->get_num_images;
477 lr->core.disk_control.replace_image_index = cb->replace_image_index;
478 lr->core.disk_control.add_image_index = cb->add_image_index;
482 case RETRO_ENVIRONMENT_SET_HW_RENDER: {
483 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] SET_HW_RENDER not supported");
487 case RETRO_ENVIRONMENT_GET_VARIABLE: {
489 if (!data)
return true;
491 struct retro_variable* var = (
struct retro_variable*)data;
493 var->value = o ? o->
value : NULL;
497 case RETRO_ENVIRONMENT_SET_VARIABLE: {
499 if (!data)
return true;
500 const struct retro_variable* var = (
const struct retro_variable*)data;
501 if (!var->key || !var->value)
return false;
505 case RETRO_ENVIRONMENT_SET_VARIABLES: {
507 if (!data)
return true;
508 const struct retro_variable* var = (
const struct retro_variable*)data;
509 for (; var->key; var++) {
510 const char* label =
"";
511 const char* defaultVal =
"";
514 char* valueBuffer = var->value ? SDL_strdup(var->value) : NULL;
515 struct retro_core_option_value* values = NULL;
518 char* semicolon = SDL_strchr(valueBuffer,
';');
522 char* labelEnd = semicolon;
523 while (labelEnd > valueBuffer && labelEnd[-1] ==
' ') {
524 *(--labelEnd) =
'\0';
528 char* opts = semicolon + 1;
529 while (*opts ==
' ') {
534 unsigned count = *opts ? 1 : 0;
535 for (
const char* p = opts; *p; p++) {
536 if (*p ==
'|') count++;
540 values = (
struct retro_core_option_value*)SDL_calloc(count + 1,
sizeof(*values));
545 while (tok && *tok) {
546 char* nextPipe = SDL_strchr(tok,
'|');
547 if (nextPipe) *nextPipe =
'\0';
548 values[vcount].value = tok;
549 values[vcount].label = NULL;
551 tok = nextPipe ? nextPipe + 1 : NULL;
555 char* firstPipe = SDL_strchr(opts,
'|');
556 if (firstPipe) *firstPipe =
'\0';
561 SDL_Libretro_InitCoreOption(lr, var->key, defaultVal, label, values, SDL_MAX_UINT32, NULL, NULL);
563 SDL_free(valueBuffer);
568 case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: {
569 if (!data)
return true;
570 *(
bool*)data = lr->core.optionsDirtyCore;
571 lr->core.optionsDirtyCore =
false;
575 case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: {
576 if (!data)
return true;
577 lr->core.supportNoGame = *(
const bool*)data;
581 case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH: {
582 if (!data)
return true;
583 *(
const char**)data = lr->core.corePath[0] ? lr->core.corePath : NULL;
587 case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK: {
588 if (!data)
return true;
589 lr->core.runloop_frame_time = *(
const struct retro_frame_time_callback*)data;
593 case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK: {
595 if (!data)
return true;
596 lr->core.audio_callback = *(
const struct retro_audio_callback*)data;
600 case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE: {
601 if (!data)
return true;
602 struct retro_rumble_interface* rumble = (
struct retro_rumble_interface*)data;
603 rumble->set_rumble_state = SDL_Libretro_SetRumbleState;
607 case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES: {
608 if (!data)
return true;
610 (1 << RETRO_DEVICE_JOYPAD) |
611 (1 << RETRO_DEVICE_MOUSE) |
612 (1 << RETRO_DEVICE_KEYBOARD) |
613 (1 << RETRO_DEVICE_LIGHTGUN) |
614 (1 << RETRO_DEVICE_ANALOG) |
615 (1 << RETRO_DEVICE_POINTER);
619 case RETRO_ENVIRONMENT_GET_LOG_INTERFACE: {
620 if (!data)
return true;
621 struct retro_log_callback* cb = (
struct retro_log_callback*)data;
622 cb->log = &SDL_Libretro_Logger;
626 case RETRO_ENVIRONMENT_GET_PERF_INTERFACE: {
627 if (!data)
return true;
628 struct retro_perf_callback* perf = (
struct retro_perf_callback*)data;
629 perf->get_time_usec = &SDL_Libretro_GetTimeUSEC;
630 perf->get_cpu_features = &SDL_Libretro_GetCPUFeatures;
631 perf->get_perf_counter = &SDL_Libretro_GetPerfCounter;
632 perf->perf_register = &SDL_Libretro_PerfRegister;
633 perf->perf_start = &SDL_Libretro_PerfStart;
634 perf->perf_stop = &SDL_Libretro_PerfStop;
635 perf->perf_log = &SDL_Libretro_PerfLog;
639 case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: {
640 if (!data)
return false;
641 const struct retro_system_av_info* av = (
const struct retro_system_av_info*)data;
642 lr->core.fps = av->timing.fps;
643 lr->core.aspectRatio = av->geometry.aspect_ratio;
646 if (av->timing.sample_rate != lr->core.sampleRate) {
647 lr->core.sampleRate = av->timing.sample_rate;
648 if (lr->core.audioStream) {
649 lr->core.audioReinitPending =
true;
656 case RETRO_ENVIRONMENT_SET_GEOMETRY: {
657 if (!data)
return true;
658 const struct retro_game_geometry* geom = (
const struct retro_game_geometry*)data;
661 if (geom->aspect_ratio != lr->core.aspectRatio) {
662 lr->core.aspectRatio = geom->aspect_ratio;
668 case RETRO_ENVIRONMENT_GET_USERNAME: {
669 if (!data)
return true;
670 *(
const char**)data = lr->username;
674 case RETRO_ENVIRONMENT_GET_LANGUAGE: {
675 if (!data)
return true;
682 case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
683 case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS_2: {
684 if (!data)
return true;
685 lr->core.serializationQuirks = *(
const uint64_t*)data;
688 *(uint64_t*)data |= RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE;
693 case RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT: {
694 if (!data)
return false;
695 *(
unsigned*)data = (
unsigned)lr->core.savestateContext;
700 case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE: {
701 if (!data)
return true;
702 struct retro_sensor_interface* sensor = (
struct retro_sensor_interface*)data;
703 sensor->set_sensor_state = SDL_Libretro_SetSensorState;
704 sensor->get_sensor_input = SDL_Libretro_GetSensorInput;
709 case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE: {
710 if (!data)
return true;
711 *(
int*)data = RETRO_AV_ENABLE_VIDEO | RETRO_AV_ENABLE_AUDIO;
716 case RETRO_ENVIRONMENT_GET_FASTFORWARDING: {
717 if (!data)
return true;
722 case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE: {
723 if (!data)
return true;
724 lr->core.fastforwardOverride = *(
const struct retro_fastforwarding_override*)data;
725 lr->core.fastforwardOverrideActive = lr->core.fastforwardOverride.fastforward;
727 if (lr->core.fastforwardOverride.fastforward) {
728 float ratio = lr->core.fastforwardOverride.ratio;
729 lr->core.speed = ratio > 1.0f ? ratio : 10.0f;
732 lr->core.speed = 1.0f;
734 if (lr->core.fastforwardOverride.notification) {
741 case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE: {
742 if (!data)
return true;
743 *(
float*)data = SDL_Libretro_GetTargetRefreshRate(lr);
748 case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: {
752 case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER: {
753 if (!data)
return false;
756 *(
unsigned*)data = RETRO_HW_CONTEXT_NONE;
760 case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: {
761 if (!data)
return false;
762 *(
unsigned*)data = 1;
766 case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS: {
767 if (!data)
return false;
773 case RETRO_ENVIRONMENT_GET_THROTTLE_STATE: {
774 if (!data)
return false;
775 struct retro_throttle_state* throttle = (
struct retro_throttle_state*)data;
777 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
778 float speed = lr->core.speed;
780 throttle->mode = RETRO_THROTTLE_FRAME_STEPPING;
781 throttle->rate = 0.0f;
783 else if (speed > 1.0f) {
784 throttle->mode = RETRO_THROTTLE_FAST_FORWARD;
785 throttle->rate = (float)(fps * speed);
787 else if (speed > 0.0f && speed < 1.0f) {
788 throttle->mode = RETRO_THROTTLE_SLOW_MOTION;
789 throttle->rate = (float)(fps * speed);
791 else if (speed < 0.0f) {
792 throttle->mode = RETRO_THROTTLE_REWINDING;
793 throttle->rate = (float)(fps * -speed);
796 throttle->mode = RETRO_THROTTLE_NONE;
797 throttle->rate = (float)fps;
800 int vsync = SDL_RENDERER_VSYNC_DISABLED;
801 if (lr->renderer && SDL_GetRenderVSync(lr->renderer, &vsync) && vsync != SDL_RENDERER_VSYNC_DISABLED) {
802 float interval = (vsync == SDL_RENDERER_VSYNC_ADAPTIVE) ? 1.0f : (float)vsync;
803 float vsyncRate = SDL_Libretro_GetTargetRefreshRate(lr) / interval;
804 if (vsyncRate < fps) {
805 throttle->mode = RETRO_THROTTLE_VSYNC;
806 throttle->rate = vsyncRate;
814 case RETRO_ENVIRONMENT_GET_JIT_CAPABLE: {
815 if (!data)
return false;
816#if defined(SDL_PLATFORM_EMSCRIPTEN)
817 *(
bool*)data =
false;
824 case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO: {
825 if (!data)
return true;
826 const struct retro_subsystem_info* info = (
const struct retro_subsystem_info*)data;
827 SDL_Libretro_FreeSubsystems(lr);
829 while (info[count].desc) count++;
831 struct retro_subsystem_info* subs = (
struct retro_subsystem_info*)SDL_calloc(count,
sizeof(*subs));
833 lr->core.subsystems = subs;
834 lr->core.subsystemCount = count;
835 for (
unsigned i = 0; i < count; i++) {
836 subs[i].desc = SDL_strdup(info[i].desc ? info[i].desc :
"");
837 subs[i].ident = SDL_strdup(info[i].ident ? info[i].ident :
"");
838 subs[i].id = info[i].id;
839 if (info[i].num_roms > 0 && info[i].roms) {
840 struct retro_subsystem_rom_info* roms = (
struct retro_subsystem_rom_info*)SDL_calloc(
846 subs[i].num_roms = info[i].num_roms;
847 for (
unsigned r = 0; r < info[i].num_roms; r++) {
848 roms[r].desc = SDL_strdup(info[i].roms[r].desc ? info[i].roms[r].desc :
"");
849 roms[r].valid_extensions = SDL_strdup(
850 info[i].roms[r].valid_extensions ? info[i].roms[r].valid_extensions :
"");
851 roms[r].need_fullpath = info[i].roms[r].need_fullpath;
852 roms[r].block_extract = info[i].roms[r].block_extract;
853 roms[r].required = info[i].roms[r].required;
854 if (info[i].roms[r].num_memory > 0 && info[i].roms[r].memory) {
855 struct retro_subsystem_memory_info* memory = (
struct retro_subsystem_memory_info*)SDL_calloc(
856 info[i].roms[r].num_memory,
858 roms[r].memory = memory;
860 roms[r].num_memory = info[i].roms[r].num_memory;
861 for (
unsigned m = 0; m < info[i].roms[r].num_memory; m++) {
862 memory[m].extension = SDL_strdup(
863 info[i].roms[r].memory[m].extension ? info[i].roms[r].memory[m].extension :
"");
864 memory[m].type = info[i].roms[r].memory[m].type;
877 case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: {
878 const struct retro_controller_info* info = (
const struct retro_controller_info*)data;
879 if (!info)
return true;
880 SDL_Libretro_FreeControllerInfo(lr);
882 for (
unsigned i = 0; info[i].types; i++) count++;
887 lr->core.controllerInfo = (
struct retro_controller_info*)SDL_calloc(count,
sizeof(*info));
888 if (lr->core.controllerInfo) {
889 lr->core.controllerInfoCount = count;
891 for (
unsigned i = 0; i < count; i++) {
892 if (info[i].num_types == 0)
continue;
893 struct retro_controller_description* types = (
struct retro_controller_description*)SDL_calloc(info[i].num_types,
sizeof(*types));
896 lr->core.controllerInfo[i].num_types = info[i].num_types;
897 for (
unsigned t = 0; t < info[i].num_types; t++) {
898 types[t].id = info[i].types[t].id;
899 types[t].desc = info[i].types[t].desc ? SDL_strdup(info[i].types[t].desc) : NULL;
902 lr->core.controllerInfo[i].types = types;
909 case RETRO_ENVIRONMENT_SET_MEMORY_MAPS: {
910 if (!data)
return true;
911 const struct retro_memory_map* map = (
const struct retro_memory_map*)data;
912 SDL_Libretro_FreeMemoryMap(lr);
913 if (map->num_descriptors > 0) {
914 size_t sz = map->num_descriptors *
sizeof(
struct retro_memory_descriptor);
915 lr->core.memoryMapDescriptors = (
struct retro_memory_descriptor*)SDL_malloc(sz);
916 if (lr->core.memoryMapDescriptors) {
917 SDL_memcpy(lr->core.memoryMapDescriptors, map->descriptors, sz);
918 lr->core.memoryMapDescriptorCount = map->num_descriptors;
920 for (
unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
921 const char* as = lr->core.memoryMapDescriptors[i].addrspace;
923 lr->core.memoryMapDescriptors[i].addrspace = SDL_strdup(as);
924 if (!lr->core.memoryMapDescriptors[i].addrspace) {
926 for (
unsigned j = i + 1; j < lr->core.memoryMapDescriptorCount; j++) {
927 lr->core.memoryMapDescriptors[j].addrspace = NULL;
929 SDL_Libretro_FreeMemoryMap(lr);
930 return SDL_SetError(
"[SDL_Libretro] Out of memory copying memory map");
938 case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: {
941 const struct retro_audio_buffer_status_callback* cb = (
const struct retro_audio_buffer_status_callback*)data;
942 lr->core.audio_buffer_status.callback = cb ? cb->callback : NULL;
946 case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: {
947 if (!data)
return true;
948 lr->core.minimumAudioLatencyMs = *(
const unsigned*)data;
950 if (lr->core.audioStream) {
951 SDL_Libretro_UpdateAudioThreshold(lr);
956 case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE: {
957 if (!data)
return false;
958 const struct retro_system_content_info_override* overrides =
959 (
const struct retro_system_content_info_override*)data;
960 SDL_Libretro_FreeContentInfoOverrides(lr);
962 while (overrides[count].extensions) count++;
964 lr->core.contentInfoOverrides = (
struct retro_system_content_info_override*)
965 SDL_calloc(count,
sizeof(
struct retro_system_content_info_override));
966 if (lr->core.contentInfoOverrides) {
967 for (
unsigned i = 0; i < count; i++) {
968 lr->core.contentInfoOverrides[i] = overrides[i];
970 lr->core.contentInfoOverrides[i].extensions = SDL_strdup(overrides[i].extensions);
972 lr->core.contentInfoOverrideCount = count;
978 case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: {
979 if (!data)
return true;
980 *(
unsigned*)data = 2;
984 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: {
985 if (!data)
return true;
986 const struct retro_core_option_definition* defs = (
const struct retro_core_option_definition*)data;
987 struct retro_core_option_v2_definition* v2defs = SDL_Libretro_CoreOptionsV1ToV2(defs);
988 if (!v2defs)
return false;
989 struct retro_core_options_v2 opts = {NULL, v2defs};
990 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &opts);
995 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: {
996 if (!data)
return false;
997 const struct retro_core_options_intl* intl = (
const struct retro_core_options_intl*)data;
998 if (!intl->us)
return false;
1000 struct retro_core_option_v2_definition* usDefs = SDL_Libretro_CoreOptionsV1ToV2(intl->us);
1001 if (!usDefs)
return false;
1002 struct retro_core_option_v2_definition* localDefs = intl->local ? SDL_Libretro_CoreOptionsV1ToV2(intl->local) : NULL;
1003 struct retro_core_options_v2 us = {NULL, usDefs};
1004 struct retro_core_options_v2 local = {NULL, localDefs};
1005 struct retro_core_options_v2_intl v2intl = {&us, localDefs ? &local : NULL};
1006 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL, &v2intl);
1007 SDL_free(localDefs);
1012 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY: {
1013 if (!data)
return false;
1014 const struct retro_core_option_display* opt = (
const struct retro_core_option_display*)data;
1015 if (!opt->key)
return false;
1016 for (
unsigned i = 0; i < lr->core.optionCount; i++) {
1017 if (lr->core.options[i].key && SDL_strcmp(lr->core.options[i].key, opt->key) == 0) {
1018 lr->core.options[i].visible = opt->visible;
1020 lr->core.optionsDirtyApp =
true;
1027 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: {
1028 if (!data)
return false;
1029 const struct retro_core_options_v2* opts = (
const struct retro_core_options_v2*)data;
1030 if (!opts->definitions) {
1035 if (opts->categories) {
1036 for (
unsigned c = 0; opts->categories[c].key; c++) {
1037 SDL_Libretro_InitCoreOptionCategory(lr, opts->categories[c].key, opts->categories[c].desc, opts->categories[c].info);
1041 for (
unsigned i = 0; opts->definitions[i].key; i++) {
1042 const struct retro_core_option_v2_definition* def = &opts->definitions[i];
1043 const char* defaultVal = def->default_value ? def->default_value :
"";
1045 const char* desc = SDL_Libretro_PickCoreOptionText(def->desc, def->desc_categorized, def->category_key);
1046 const char* info = SDL_Libretro_PickCoreOptionText(def->info, def->info_categorized, def->category_key);
1047 SDL_Libretro_InitCoreOption(lr, def->key, defaultVal, desc, def->values, RETRO_NUM_CORE_OPTION_VALUES_MAX, info, def->category_key);
1052 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL: {
1053 if (!data)
return false;
1054 const struct retro_core_options_v2_intl* intl = (
const struct retro_core_options_v2_intl*)data;
1055 if (!intl->us)
return false;
1056 const struct retro_core_options_v2* us = intl->us;
1057 const struct retro_core_options_v2* local = intl->local;
1059 if (!local || !us->definitions || (!local->definitions && !local->categories)) {
1060 return SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, (
void*)us);
1064 unsigned defCount = 0;
1065 while (us->definitions[defCount].key) defCount++;
1066 unsigned catCount = 0;
1067 while (us->categories && us->categories[catCount].key) catCount++;
1068 struct retro_core_option_v2_definition* defs =
1069 (
struct retro_core_option_v2_definition*)SDL_calloc(defCount + 1,
sizeof(*defs));
1070 struct retro_core_option_v2_category* cats = catCount > 0
1071 ? (
struct retro_core_option_v2_category*)SDL_calloc(catCount + 1,
sizeof(*cats))
1073 if (!defs || (catCount > 0 && !cats)) {
1079 for (
unsigned c = 0; c < catCount; c++) {
1080 cats[c] = us->categories[c];
1081 for (
unsigned l = 0; local->categories && local->categories[l].key; l++) {
1082 if (SDL_strcmp(local->categories[l].key, cats[c].key) == 0) {
1083 cats[c].desc = SDL_Libretro_PickLocalText(cats[c].desc, local->categories[l].desc);
1084 cats[c].info = SDL_Libretro_PickLocalText(cats[c].info, local->categories[l].info);
1090 for (
unsigned i = 0; i < defCount; i++) {
1091 defs[i] = us->definitions[i];
1092 const struct retro_core_option_v2_definition* loc = NULL;
1093 for (
unsigned l = 0; local->definitions && local->definitions[l].key; l++) {
1094 if (SDL_strcmp(local->definitions[l].key, defs[i].key) == 0) {
1095 loc = &local->definitions[l];
1100 defs[i].desc = SDL_Libretro_PickLocalText(defs[i].desc, loc->desc);
1101 defs[i].desc_categorized = SDL_Libretro_PickLocalText(defs[i].desc_categorized, loc->desc_categorized);
1102 defs[i].info = SDL_Libretro_PickLocalText(defs[i].info, loc->info);
1103 defs[i].info_categorized = SDL_Libretro_PickLocalText(defs[i].info_categorized, loc->info_categorized);
1104 for (
unsigned v = 0; v < RETRO_NUM_CORE_OPTION_VALUES_MAX && defs[i].values[v].value; v++) {
1105 for (
unsigned lv = 0; lv < RETRO_NUM_CORE_OPTION_VALUES_MAX && loc->values[lv].value; lv++) {
1106 if (SDL_strcmp(loc->values[lv].value, defs[i].values[v].value) == 0) {
1107 defs[i].values[v].label = SDL_Libretro_PickLocalText(defs[i].values[v].label, loc->values[lv].label);
1114 struct retro_core_options_v2 merged = {cats, defs};
1115 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &merged);
1121 case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: {
1122 if (!data)
return true;
1123 *(
unsigned*)data = 1;
1127 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK: {
1129 lr->core.optionsUpdateDisplayCallback = NULL;
1132 const struct retro_core_options_update_display_callback* cb =
1133 (
const struct retro_core_options_update_display_callback*)data;
1134 lr->core.optionsUpdateDisplayCallback = cb->callback;
1138 case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: {
1139 const struct retro_message_ext* msg = (
const struct retro_message_ext*)data;
1140 if (!msg || !msg->msg)
return true;
1141 double seconds = msg->duration / 1000.0;
1143 if (msg->target == RETRO_MESSAGE_TARGET_LOG || msg->target == RETRO_MESSAGE_TARGET_ALL) {
1144 switch (msg->level) {
1145 case RETRO_LOG_DEBUG: SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
"[Core] %s", msg->msg);
break;
1146 case RETRO_LOG_WARN: SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[Core] %s", msg->msg);
break;
1147 case RETRO_LOG_ERROR: SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"[Core] %s", msg->msg);
break;
1148 default: SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"[Core] %s", msg->msg);
break;
1152 if (msg->target != RETRO_MESSAGE_TARGET_LOG) {
1153 SDL_Libretro_OsdPush(lr, msg->msg, seconds, msg->priority, msg->type, msg->progress);
1158 case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE: {
1159 if (!data)
return true;
1160 lr->core.disk_control = *(
const struct retro_disk_control_ext_callback*)data;
1164 case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: {
1165 if (!data)
return false;
1167 if (lr->core.gameInfoExtArray) {
1168 *(
const struct retro_game_info_ext**)data = lr->core.gameInfoExtArray;
1172 if (!lr->core.gameInfoExt.full_path)
return false;
1173 *(
const struct retro_game_info_ext**)data = &lr->core.gameInfoExt;
1178 case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER: {
1179 if (!data || !lr->core.texture)
return false;
1180 struct retro_framebuffer* fb = (
struct retro_framebuffer*)data;
1181 if (fb->width != lr->core.width || fb->height != lr->core.height)
return false;
1183 if (fb->access_flags & RETRO_MEMORY_ACCESS_READ)
return false;
1186 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
1188 void* pixels = NULL;
1190 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &pitch))
return false;
1193 fb->pitch = (size_t)pitch;
1194 fb->format = lr->core.pixelFormat;
1195 fb->memory_flags = RETRO_MEMORY_TYPE_CACHED;
1196 lr->core.softwareFramebufferPixels = pixels;
1201 case RETRO_ENVIRONMENT_GET_VFS_INTERFACE: {
1202 if (!data)
return true;
1204 if (!lr->vfs_interface.open)
return false;
1205 struct retro_vfs_interface_info* info = (
struct retro_vfs_interface_info*)data;
1206 if (info->required_interface_version > 4) {
1207 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] GET_VFS_INTERFACE: unsupported required_interface_version %u", info->required_interface_version);
1212 info->required_interface_version = 4;
1213 info->iface = &lr->vfs_interface;
1214 lr->core.usedVFS =
true;
1219 case RETRO_ENVIRONMENT_GET_MICROPHONE_INTERFACE: {
1220 if (!data)
return false;
1221 struct retro_microphone_interface* mic = (
struct retro_microphone_interface*)data;
1222 mic->interface_version = RETRO_MICROPHONE_INTERFACE_VERSION;
1223 mic->open_mic = SDL_Libretro_MicOpen;
1224 mic->close_mic = SDL_Libretro_MicClose;
1225 mic->get_params = SDL_Libretro_MicGetParams;
1226 mic->set_mic_state = SDL_Libretro_MicSetState;
1227 mic->get_mic_state = SDL_Libretro_MicGetState;
1228 mic->read_mic = SDL_Libretro_MicRead;
1233 case RETRO_ENVIRONMENT_GET_DEVICE_POWER: {
1234 if (!data)
return true;
1235 struct retro_device_power* power = (
struct retro_device_power*)data;
1237 SDL_PowerState sdl_state = SDL_GetPowerInfo(&power->seconds, &percent);
1238 switch (sdl_state) {
1239 case SDL_POWERSTATE_ON_BATTERY:
1240 power->state = RETRO_POWERSTATE_DISCHARGING;
1242 case SDL_POWERSTATE_CHARGING:
1243 power->state = RETRO_POWERSTATE_CHARGING;
1245 case SDL_POWERSTATE_CHARGED:
1246 power->state = RETRO_POWERSTATE_CHARGED;
1248 case SDL_POWERSTATE_NO_BATTERY:
1249 power->state = RETRO_POWERSTATE_PLUGGED_IN;
1254 power->percent = (int8_t)percent;
1259 case RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE: {
1260 if (!data)
return true;
1262 if (lr->core.audioStream && SDL_GetAudioStreamFormat(lr->core.audioStream, NULL, &spec) && spec.freq > 0) {
1264 *(
unsigned*)data = (
unsigned)spec.freq;
1266 else if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL) && spec.freq > 0) {
1268 *(
unsigned*)data = (
unsigned)spec.freq;
1278 case RETRO_ENVIRONMENT_GET_LED_INTERFACE: {
1279 if (!data)
return true;
1280 struct retro_led_interface* iface = (
struct retro_led_interface*)data;
1281 iface->set_led_state = SDL_Libretro_SetLEDState;
1290 if (SDL_Libretro_PushEnvEvent(lr, cmd, data)) {
1293 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Unhandled environment callback: %u", cmd);
#define SDL_LIBRETRO_MAX_USERS
The number of controller ports (gamepads) the frontend tracks.
#define SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE
Fallback rate used when a core reports no audio sample rate (<= 0).
void SDL_Libretro_SetMessage(SDL_Libretro *lr, const char *msg, double duration)
Add a message to be displayed within the libretro context.
#define SDL_EVENT_LIBRETRO_SHUTDOWN
The core requested shutdown.
float SDL_Libretro_GetSpeed(const SDL_Libretro *lr)
Get how fast the game runs, as a multiple of its normal speed.
struct SDL_Libretro SDL_Libretro
A libretro frontend context: one loaded core, its game, and the settings that carry across cores.
bool SDL_Libretro_SetOptionValue(SDL_Libretro *lr, const char *key, const char *value)
Set a core option's value; fails if the value isn't one the core declared.
int SDL_Libretro_GetLanguage(const SDL_Libretro *lr)
Get the language reported to cores through RETRO_ENVIRONMENT_GET_LANGUAGE.
#define SDL_EVENT_LIBRETRO_GEOMETRY_CHANGED
The video size, aspect ratio, or timing changed mid-game.
const SDL_LibretroOption * SDL_Libretro_GetOption(const SDL_Libretro *lr, const char *key)
Retrieve details about a given core option.
void SDL_Libretro_SetLanguage(SDL_Libretro *lr, int language)
Override the language reported to cores through RETRO_ENVIRONMENT_GET_LANGUAGE.
A core option and its current state.
const char * value
The current value.
const char * key
Unique identifier the core queries.