SDL_Libretro 0.1.0
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_env.h
Go to the documentation of this file.
1
7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_ENV_IMPL_ONCE)
8#define SDL_LIBRETRO_ENV_IMPL_ONCE
9
10#include <stdarg.h>
11
16static void SDL_Libretro_Logger(enum retro_log_level level, const char* fmt, ...) {
17 if (SDL_Libretro_active && level < SDL_Libretro_active->logLevel) {
18 return;
19 }
20
21 va_list args;
22 va_start(args, fmt);
23 char buf[2048];
24 SDL_vsnprintf(buf, sizeof(buf), fmt, args);
25 va_end(args);
26
27 /* Strip trailing newline */
28 size_t len = SDL_strlen(buf);
29 if (len > 0 && buf[len - 1] == '\n') buf[len - 1] = '\0';
30
31 switch (level) {
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;
35 case RETRO_LOG_DEBUG:
36 default: SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s", buf); break;
37 }
38}
39
40static retro_time_t SDL_Libretro_GetTimeUSEC(void) {
41 // SDL_GetTicksNS() avoids the overflow of scaling the raw performance counter by 1000000.
42 return (retro_time_t)(SDL_GetTicksNS() / SDL_NS_PER_US);
43}
44
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;
57 if (SDL_HasNEON()) {
58 features |= RETRO_SIMD_NEON;
59#if defined(__aarch64__) || defined(_M_ARM64)
60 // AArch64's Advanced SIMD is NEON; report both, as RetroArch does.
61 features |= RETRO_SIMD_ASIMD;
62#endif
63 }
64 return features;
65}
66
67static retro_perf_tick_t SDL_Libretro_GetPerfCounter(void) {
68 return (retro_perf_tick_t)SDL_GetPerformanceCounter();
69}
70
71static void SDL_Libretro_PerfRegister(struct retro_perf_counter* counter) {
72 SDL_Libretro* lr = SDL_Libretro_active;
73 if (!lr || !counter) return;
74
75 // Avoid registering the same counter twice.
76 for (unsigned i = 0; i < lr->core.perfCounterCount; i++) {
77 if (lr->core.perfCounters[i] == counter) {
78 counter->registered = true;
79 return;
80 }
81 }
82
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 : "?");
85 return;
86 }
87
88 lr->core.perfCounters[lr->core.perfCounterCount++] = counter;
89 counter->registered = true;
90}
91
92static void SDL_Libretro_PerfStart(struct retro_perf_counter* counter) {
93 if (!counter) return;
94 counter->call_cnt++;
95 counter->start = (retro_perf_tick_t)SDL_GetPerformanceCounter();
96}
97
98static void SDL_Libretro_PerfStop(struct retro_perf_counter* counter) {
99 if (counter) counter->total += (retro_perf_tick_t)SDL_GetPerformanceCounter() - counter->start;
100}
101
106static void SDL_Libretro_PerfLog(void) {
107 SDL_Libretro* lr = SDL_Libretro_active;
108 if (!lr) return;
109
110 // SDL_GetPerformanceCounter() is the tick source used by the perf counters, so its frequency converts accumulated ticks to seconds. */
111 Uint64 freq = SDL_GetPerformanceFrequency();
112
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;
116
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);
119 }
120}
121
128static void SDL_Libretro_ApplyRumble(SDL_Libretro* lr, unsigned port) {
129 if (!lr || port >= SDL_LIBRETRO_MAX_USERS || !lr->gamepads[port]) return;
130 // SDL's low-frequency motor is the large, strong one.
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);
134}
135
140static void SDL_Libretro_StopRumble(SDL_Libretro* lr) {
141 if (!lr) return;
142 for (unsigned port = 0; port < SDL_LIBRETRO_MAX_USERS; port++) {
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);
147 }
148}
149
150static bool SDL_Libretro_SetRumbleState(unsigned port, enum retro_rumble_effect effect, uint16_t strength) {
151 SDL_Libretro* lr = SDL_Libretro_active;
152 if (!lr || port >= SDL_LIBRETRO_MAX_USERS) return false;
153
154 float normalized = (float)strength / 65535.0f;
155 if (effect == RETRO_RUMBLE_STRONG) {
156 lr->core.rumbleStrong[port] = normalized;
157 }
158 else {
159 lr->core.rumbleWeak[port] = normalized;
160 }
161
162 SDL_Libretro_ApplyRumble(lr, port);
163
164 return true;
165}
166
167static void SDL_Libretro_SetLEDState(int led, int state) {
168 SDL_Libretro* lr = SDL_Libretro_active;
169 if (!lr) return;
170 Uint8 v = state ? 255 : 0;
171 if (led >= 0 && led < SDL_LIBRETRO_MAX_USERS) {
172 if (lr->gamepads[led]) SDL_SetGamepadLED(lr->gamepads[led], v, v, v);
173 }
174 else {
175 for (unsigned i = 0; i < SDL_LIBRETRO_MAX_USERS; i++) {
176 if (lr->gamepads[i]) SDL_SetGamepadLED(lr->gamepads[i], v, v, v);
177 }
178 }
179}
180
181static const char* SDL_Libretro_GetDirectory(SDL_Libretro* lr, unsigned cmd) {
182 switch (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;
193 default:
194 return NULL;
195 }
196}
197
203static float SDL_Libretro_GetTargetRefreshRate(SDL_Libretro* lr) {
204 float rate = 60.0f;
205 if (lr->window) {
206 SDL_DisplayID display = SDL_GetDisplayForWindow(lr->window);
207 if (display) {
208 const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
209 if (mode && mode->refresh_rate > 0.0f) {
210 rate = mode->refresh_rate;
211 }
212 }
213 }
214 return rate;
215}
216
223static const char* SDL_Libretro_PickCoreOptionText(const char* base, const char* categorized, const char* categoryKey) {
224 if (categoryKey && categoryKey[0] && categorized && categorized[0]) {
225 return categorized;
226 }
227 return base;
228}
229
237static int SDL_Libretro_LanguageFromLocale(const char* language, const char* country) {
238 if (!language || !language[0]) return RETRO_LANGUAGE_ENGLISH;
239 if (!country) country = "";
240
241 // Catalan's Valencian variant isn't a country, but some platforms tack it on as "ca-ES-valencia" or "ca_ES@valencia".
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;
245 }
246 if (SDL_strcasecmp(language, "en") == 0) {
247 return SDL_strcasecmp(country, "GB") == 0 ? RETRO_LANGUAGE_BRITISH_ENGLISH : RETRO_LANGUAGE_ENGLISH;
248 }
249 if (SDL_strcasecmp(language, "pt") == 0) {
250 return SDL_strcasecmp(country, "PT") == 0 ? RETRO_LANGUAGE_PORTUGUESE_PORTUGAL : RETRO_LANGUAGE_PORTUGUESE_BRAZIL;
251 }
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;
255 }
256
257 static const struct {
258 const char* code;
259 int language;
260 } languages[] = {
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}, // Legacy code for Hebrew
279 {"ast", RETRO_LANGUAGE_ASTURIAN},
280 {"fi", RETRO_LANGUAGE_FINNISH},
281 {"id", RETRO_LANGUAGE_INDONESIAN},
282 {"in", RETRO_LANGUAGE_INDONESIAN}, // Legacy code for 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},
294 };
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;
298 }
299 }
300 return RETRO_LANGUAGE_ENGLISH;
301}
302
307static int SDL_Libretro_DetectLanguage(void) {
308 int count = 0;
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);
313 }
314 SDL_free(locales);
315 return language;
316}
317
318void SDL_Libretro_SetLanguage(SDL_Libretro* lr, int language) {
319 if (!lr) return;
320 lr->language = (language >= 0 && language < RETRO_LANGUAGE_LAST) ? language : -1;
321}
322
323int SDL_Libretro_GetLanguage(const SDL_Libretro* lr) {
324 if (!lr) return RETRO_LANGUAGE_ENGLISH;
325 return lr->language >= 0 ? lr->language : SDL_Libretro_DetectLanguage();
326}
327
332static const char* SDL_Libretro_PickLocalText(const char* us, const char* local) {
333 return (local && local[0]) ? local : us;
334}
335
340static struct retro_core_option_v2_definition* SDL_Libretro_CoreOptionsV1ToV2(const struct retro_core_option_definition* defs) {
341 unsigned count = 0;
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));
352 }
353 return v2defs;
354}
355
356static bool SDL_Libretro_EnvironmentCallback(unsigned cmd, void* data) {
357 SDL_Libretro* lr = SDL_Libretro_active;
358 if (!lr) return false;
359
360 switch (cmd) {
361 case RETRO_ENVIRONMENT_SET_ROTATION: {
362 if (!data) return false;
363 // Restrict rotation to 0-3
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);
366 return true;
367 }
368
369 case RETRO_ENVIRONMENT_GET_OVERSCAN: {
370 return false;
371 }
372
373 case RETRO_ENVIRONMENT_GET_CAN_DUPE: {
374 if (!data) return false;
375 *(bool*)data = true;
376 return true;
377 }
378
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);
383 SDL_Libretro_SetMessage(lr, msg->msg, seconds);
384 return true;
385 }
386
387 case RETRO_ENVIRONMENT_SHUTDOWN: {
388 SDL_Log("[SDL_Libretro] Shutdown requested");
389 lr->core.shutdown = true;
390 SDL_Libretro_PushEvent(lr, SDL_EVENT_LIBRETRO_SHUTDOWN, NULL);
391 return true;
392 }
393
394 case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL: {
395 if (!data) return false;
396 lr->core.performanceLevel = *(const unsigned*)data;
397 return true;
398 }
399
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: {
405 // If data is NULL, it's checking if the environment callback is available.
406 if (!data) return true;
407 *(const char**)data = SDL_Libretro_GetDirectory(lr, cmd);
408 return true;
409 }
410
411 case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: {
412 if (!data) return false;
413 enum retro_pixel_format fmt = *(const enum retro_pixel_format*)data;
414 switch (fmt) {
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;
420 // The texture's format is fixed at creation. The load path builds it from this field, a runtime change (texture already exists) defers a rebuild to the next RunFrame.
421 if (changed && lr->core.texture) {
422 lr->core.videoReinitPending = true;
423 }
424 return true;
425 }
426 default:
427 // Unsupported. Signal false without clobbering the current format.
428 return false;
429 }
430 }
431
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;
435
436 // Clear out the existing input descriptors first
437 SDL_Libretro_FreeInputDescriptors(lr);
438
439 // Count how many we are adding.
440 unsigned count = 0;
441 for (const struct retro_input_descriptor* d = desc; d->description; d++) count++;
442 if (count == 0) {
443 return true;
444 }
445
446 // Copy the input descriptors into our structure
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");
450 }
451 SDL_memcpy(lr->core.inputDescriptors, desc, count * sizeof(*desc));
452 lr->core.inputDescriptorsCount = count;
453 // Deep copy the description strings. SDL_strdup returns NULL on failure, which FreeInputDescriptors tolerates.
454 for (unsigned i = 0; i < count; i++) {
455 lr->core.inputDescriptors[i].description = SDL_strdup(desc[i].description);
456 }
457
458 return true;
459 }
460
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;
465 return true;
466 }
467
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;
479 return true;
480 }
481
482 case RETRO_ENVIRONMENT_SET_HW_RENDER: {
483 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] SET_HW_RENDER not supported");
484 return false;
485 }
486
487 case RETRO_ENVIRONMENT_GET_VARIABLE: {
488 // When NULL data is passed, the core is checking if the environment callback is available.
489 if (!data) return true;
490 // An unknown key still succeeds, with a NULL value.
491 struct retro_variable* var = (struct retro_variable*)data;
492 const SDL_LibretroOption* o = var->key ? SDL_Libretro_GetOption(lr, var->key) : NULL;
493 var->value = o ? o->value : NULL;
494 return true;
495 }
496
497 case RETRO_ENVIRONMENT_SET_VARIABLE: {
498 // When NULL data is passed, it's just checking if the callback is available.
499 if (!data) return true;
500 const struct retro_variable* var = (const struct retro_variable*)data;
501 if (!var->key || !var->value) return false;
502 return SDL_Libretro_SetOptionValue(lr, var->key, var->value);
503 }
504
505 case RETRO_ENVIRONMENT_SET_VARIABLES: {
506 // When NULL data is passed, it's just checking if the callback is available.
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 = "";
512 // Copy the value so that we can manipulate it as needed.
513 // Only needed until the end of this call.
514 char* valueBuffer = var->value ? SDL_strdup(var->value) : NULL;
515 struct retro_core_option_value* values = NULL;
516 unsigned vcount = 0;
517 if (valueBuffer) {
518 char* semicolon = SDL_strchr(valueBuffer, ';');
519 if (semicolon) {
520 // Split off the label, trimming trailing spaces.
521 *semicolon = '\0';
522 char* labelEnd = semicolon;
523 while (labelEnd > valueBuffer && labelEnd[-1] == ' ') {
524 *(--labelEnd) = '\0';
525 }
526 label = valueBuffer;
527
528 char* opts = semicolon + 1;
529 while (*opts == ' ') {
530 opts++;
531 }
532
533 // Count the pipe-separated values so the array is sized exactly.
534 unsigned count = *opts ? 1 : 0;
535 for (const char* p = opts; *p; p++) {
536 if (*p == '|') count++;
537 }
538 if (count > 0) {
539 // Extra trailing entry keeps a NULL sentinel for InitCoreOption.
540 values = (struct retro_core_option_value*)SDL_calloc(count + 1, sizeof(*values));
541 }
542 if (values) {
543 // Version 1 and 2 of the variables API carry no per-value labels, only value strings.
544 char* tok = opts;
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;
550 vcount++;
551 tok = nextPipe ? nextPipe + 1 : NULL;
552 }
553 }
554 // The first value is the default (terminate it in place).
555 char* firstPipe = SDL_strchr(opts, '|');
556 if (firstPipe) *firstPipe = '\0';
557 defaultVal = opts;
558 }
559 }
560 // The legacy list has no size limit, but always ends in a NULL value.
561 SDL_Libretro_InitCoreOption(lr, var->key, defaultVal, label, values, SDL_MAX_UINT32, NULL, NULL);
562 SDL_free(values);
563 SDL_free(valueBuffer);
564 }
565 return true;
566 }
567
568 case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: {
569 if (!data) return true;
570 *(bool*)data = lr->core.optionsDirtyCore;
571 lr->core.optionsDirtyCore = false;
572 return true;
573 }
574
575 case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: {
576 if (!data) return true;
577 lr->core.supportNoGame = *(const bool*)data;
578 return true;
579 }
580
581 case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH: {
582 if (!data) return true;
583 *(const char**)data = lr->core.corePath[0] ? lr->core.corePath : NULL;
584 return true;
585 }
586
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;
590 return true;
591 }
592
593 case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK: {
594 // When data is NULL, the core is checking if the environment callback is available.
595 if (!data) return true;
596 lr->core.audio_callback = *(const struct retro_audio_callback*)data;
597 return true;
598 }
599
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;
604 return true;
605 }
606
607 case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES: {
608 if (!data) return true;
609 *(uint64_t*)data =
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);
616 return true;
617 }
618
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;
623 return true;
624 }
625
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;
636 return true;
637 }
638
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;
644
645 // A base-resolution change is picked up by VideoRefresh on the next frame; aspect is applied at render time. Only the audio stream (frequency fixed at open) needs an explicit reopen, deferred to RunFrame.
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;
650 }
651 }
652 SDL_Libretro_PushEvent(lr, SDL_EVENT_LIBRETRO_GEOMETRY_CHANGED, NULL);
653 return true;
654 }
655
656 case RETRO_ENVIRONMENT_SET_GEOMETRY: {
657 if (!data) return true;
658 const struct retro_game_geometry* geom = (const struct retro_game_geometry*)data;
659 // Geometry updates during runtime are applied in SDL_Libretro_VideoRefresh().
660 // Cores may call this every frame, so only report actual changes.
661 if (geom->aspect_ratio != lr->core.aspectRatio) {
662 lr->core.aspectRatio = geom->aspect_ratio;
663 SDL_Libretro_PushEvent(lr, SDL_EVENT_LIBRETRO_GEOMETRY_CHANGED, NULL);
664 }
665 return true;
666 }
667
668 case RETRO_ENVIRONMENT_GET_USERNAME: {
669 if (!data) return true;
670 *(const char**)data = lr->username;
671 return true;
672 }
673
674 case RETRO_ENVIRONMENT_GET_LANGUAGE: {
675 if (!data) return true;
676 *(unsigned*)data = (unsigned)SDL_Libretro_GetLanguage(lr);
677 return true;
678 }
679
680 // SET_SERIALIZATION_QUIRKS_2 is a newer number for the same command,
681 // with the same data; cores use either.
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;
686 // Report that we support variable-size states so cores that flag
687 // RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE know we won't break.
688 *(uint64_t*)data |= RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE;
689 return true;
690 }
691
692 case 72:
693 case RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT: {
694 if (!data) return false;
695 *(unsigned*)data = (unsigned)lr->core.savestateContext;
696 return true;
697 }
698
699 case 25:
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;
705 return true;
706 }
707
708 case 47:
709 case RETRO_ENVIRONMENT_GET_AUDIO_VIDEO_ENABLE: {
710 if (!data) return true;
711 *(int*)data = RETRO_AV_ENABLE_VIDEO | RETRO_AV_ENABLE_AUDIO;
712 return true;
713 }
714
715 case 49:
716 case RETRO_ENVIRONMENT_GET_FASTFORWARDING: {
717 if (!data) return true;
718 *(bool*)data = SDL_Libretro_GetSpeed(lr) > 1.0f;
719 return true;
720 }
721
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;
726 // Bypass SDL_Libretro_SetSpeed so the inhibit_toggle guard doesn't block the core's own request.
727 if (lr->core.fastforwardOverride.fastforward) {
728 float ratio = lr->core.fastforwardOverride.ratio;
729 lr->core.speed = ratio > 1.0f ? ratio : 10.0f;
730 }
731 else {
732 lr->core.speed = 1.0f;
733 }
734 if (lr->core.fastforwardOverride.notification) {
735 SDL_Libretro_SetMessage(lr, lr->core.fastforwardOverride.fastforward ? "Fast-Forward" : "Normal Speed", 3.0);
736 }
737 return true;
738 }
739
740 case 50:
741 case RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE: {
742 if (!data) return true;
743 *(float*)data = SDL_Libretro_GetTargetRefreshRate(lr);
744 return true;
745 }
746
747 case 51:
748 case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: {
749 return true;
750 }
751
752 case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER: {
753 if (!data) return false;
754 // Software rendering only; reporting RETRO_HW_CONTEXT_NONE lets
755 // GL/Vulkan-capable cores fall back to their software path.
756 *(unsigned*)data = RETRO_HW_CONTEXT_NONE;
757 return true;
758 }
759
760 case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: {
761 if (!data) return false;
762 *(unsigned*)data = 1;
763 return true;
764 }
765
766 case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS: {
767 if (!data) return false;
768 *(unsigned*)data = SDL_LIBRETRO_MAX_USERS;
769 return true;
770 }
771
772 case 71:
773 case RETRO_ENVIRONMENT_GET_THROTTLE_STATE: {
774 if (!data) return false;
775 struct retro_throttle_state* throttle = (struct retro_throttle_state*)data;
776
777 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
778 float speed = lr->core.speed;
779 if (speed == 0.0f) {
780 throttle->mode = RETRO_THROTTLE_FRAME_STEPPING;
781 throttle->rate = 0.0f;
782 }
783 else if (speed > 1.0f) {
784 throttle->mode = RETRO_THROTTLE_FAST_FORWARD;
785 throttle->rate = (float)(fps * speed);
786 }
787 else if (speed > 0.0f && speed < 1.0f) {
788 throttle->mode = RETRO_THROTTLE_SLOW_MOTION;
789 throttle->rate = (float)(fps * speed);
790 }
791 else if (speed < 0.0f) {
792 throttle->mode = RETRO_THROTTLE_REWINDING;
793 throttle->rate = (float)(fps * -speed);
794 }
795 else {
796 throttle->mode = RETRO_THROTTLE_NONE;
797 throttle->rate = (float)fps;
798
799 // VSYNC
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;
807 }
808 }
809 }
810
811 return true;
812 }
813
814 case RETRO_ENVIRONMENT_GET_JIT_CAPABLE: {
815 if (!data) return false;
816#if defined(SDL_PLATFORM_EMSCRIPTEN)
817 *(bool*)data = false;
818#else
819 *(bool*)data = true;
820#endif
821 return true;
822 }
823
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);
828 unsigned count = 0;
829 while (info[count].desc) count++;
830 if (count > 0) {
831 struct retro_subsystem_info* subs = (struct retro_subsystem_info*)SDL_calloc(count, sizeof(*subs));
832 if (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(
841 info[i].num_roms,
842 sizeof(*roms));
843 subs[i].roms = roms;
844 if (roms) {
845 // Only publish the count once the array exists, so an allocation failure leaves num_roms at 0.
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,
857 sizeof(*memory));
858 roms[r].memory = memory;
859 if (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;
865 }
866 }
867 }
868 }
869 }
870 }
871 }
872 }
873 }
874 return true;
875 }
876
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);
881 unsigned count = 0;
882 for (unsigned i = 0; info[i].types; i++) count++;
883 if (count == 0) {
884 return true;
885 }
886
887 lr->core.controllerInfo = (struct retro_controller_info*)SDL_calloc(count, sizeof(*info));
888 if (lr->core.controllerInfo) {
889 lr->core.controllerInfoCount = count;
890 // Deep copy the data into our own data struture
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));
894 if (types) {
895 // Only publish the count once the array exists, so an allocation failure leaves num_types at 0.
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;
900 }
901 }
902 lr->core.controllerInfo[i].types = types;
903 }
904 }
905 return true;
906 }
907
908 case 36:
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;
919 // The core only guarantees the addrspace label strings for the duration of this call, so deep-copy them.
920 for (unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
921 const char* as = lr->core.memoryMapDescriptors[i].addrspace;
922 if (!as) continue;
923 lr->core.memoryMapDescriptors[i].addrspace = SDL_strdup(as);
924 if (!lr->core.memoryMapDescriptors[i].addrspace) {
925 // Clear the not-yet-copied labels (still the core's temporary pointers) before unwinding.
926 for (unsigned j = i + 1; j < lr->core.memoryMapDescriptorCount; j++) {
927 lr->core.memoryMapDescriptors[j].addrspace = NULL;
928 }
929 SDL_Libretro_FreeMemoryMap(lr);
930 return SDL_SetError("[SDL_Libretro] Out of memory copying memory map");
931 }
932 }
933 }
934 }
935 return true;
936 }
937
938 case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: {
939 // NULL data unregisters the callback.
940 // @see SDL_Libretro_ReportAudioBufferStatus
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;
943 return true;
944 }
945
946 case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: {
947 if (!data) return true;
948 lr->core.minimumAudioLatencyMs = *(const unsigned*)data;
949 // Apply the new latency target live if audio is already running.
950 if (lr->core.audioStream) {
951 SDL_Libretro_UpdateAudioThreshold(lr);
952 }
953 return true;
954 }
955
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);
961 unsigned count = 0;
962 while (overrides[count].extensions) count++;
963 if (count > 0) {
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];
969 // The core only guarantees the extensions string for this call, so deep-copy it.
970 lr->core.contentInfoOverrides[i].extensions = SDL_strdup(overrides[i].extensions);
971 }
972 lr->core.contentInfoOverrideCount = count;
973 }
974 }
975 return true;
976 }
977
978 case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: {
979 if (!data) return true;
980 *(unsigned*)data = 2;
981 return true;
982 }
983
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);
991 SDL_free(v2defs);
992 return result;
993 }
994
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;
999 // Convert both sets to v2 and let the v2 INTL path merge the translations.
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);
1008 SDL_free(usDefs);
1009 return result;
1010 }
1011
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;
1019 // Only the app needs to refresh; the option's value is unchanged, so don't make GET_VARIABLE_UPDATE report true to the core.
1020 lr->core.optionsDirtyApp = true;
1021 return true;
1022 }
1023 }
1024 return false;
1025 }
1026
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) {
1031 return true; // There arn't any options.
1032 }
1033
1034 // Register the option categories (if any) before the options that reference them.
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);
1038 }
1039 }
1040
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 : "";
1044 // Prefer the *_categorized text when the option belongs to a category.
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);
1048 }
1049 return true;
1050 }
1051
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;
1058 // Without translations the `us` member is already a retro_core_options_v2, so forward it as-is.
1059 if (!local || !us->definitions || (!local->definitions && !local->categories)) {
1060 return SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, (void*)us);
1061 }
1062
1063 // Merge onto a copy of `us`: keys, option values, defaults and category membership come from `us`; any non-empty text in `local` (matched by key, and value labels by value) replaces the English text. Options missing from `local` keep their `us` text.
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))
1072 : NULL;
1073 if (!defs || (catCount > 0 && !cats)) {
1074 SDL_free(defs);
1075 SDL_free(cats);
1076 return false;
1077 }
1078
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);
1085 break;
1086 }
1087 }
1088 }
1089
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];
1096 break;
1097 }
1098 }
1099 if (!loc) continue;
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);
1108 break;
1109 }
1110 }
1111 }
1112 }
1113
1114 struct retro_core_options_v2 merged = {cats, defs};
1115 bool result = SDL_Libretro_EnvironmentCallback(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2, &merged);
1116 SDL_free(cats);
1117 SDL_free(defs);
1118 return result;
1119 }
1120
1121 case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: {
1122 if (!data) return true;
1123 *(unsigned*)data = 1;
1124 return true;
1125 }
1126
1127 case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK: {
1128 if (!data) {
1129 lr->core.optionsUpdateDisplayCallback = NULL;
1130 return true;
1131 }
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;
1135 return true;
1136 }
1137
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;
1142
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;
1149 }
1150 }
1151
1152 if (msg->target != RETRO_MESSAGE_TARGET_LOG) {
1153 SDL_Libretro_OsdPush(lr, msg->msg, seconds, msg->priority, msg->type, msg->progress);
1154 }
1155 return true;
1156 }
1157
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;
1161 return true;
1162 }
1163
1164 case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: {
1165 if (!data) return false;
1166 // Subsystem loads get an array with one entry per content file.
1167 if (lr->core.gameInfoExtArray) {
1168 *(const struct retro_game_info_ext**)data = lr->core.gameInfoExtArray;
1169 return true;
1170 }
1171 // Single-file loads get a 1-entry array. A non-NULL full_path means LoadGame populated it for content.
1172 if (!lr->core.gameInfoExt.full_path) return false;
1173 *(const struct retro_game_info_ext**)data = &lr->core.gameInfoExt;
1174 return true;
1175 }
1176
1177 case 40:
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;
1182 // The lock is write-only (discards prior contents), so a core asking to read back can't use it.
1183 if (fb->access_flags & RETRO_MEMORY_ACCESS_READ) return false;
1184
1185 // Drop any lock still held from an earlier acquire this frame (or a frame that never reached video_cb) before re-locking.
1186 SDL_Libretro_ReleaseSoftwareFramebuffer(lr);
1187
1188 void* pixels = NULL;
1189 int pitch = 0;
1190 if (!SDL_LockTexture(lr->core.texture, NULL, &pixels, &pitch)) return false;
1191
1192 fb->data = pixels;
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;
1197 return true;
1198 }
1199
1200 case 45:
1201 case RETRO_ENVIRONMENT_GET_VFS_INTERFACE: {
1202 if (!data) return true;
1203 // No VFS to hand out (SDL_LIBRETRO_DISABLE_VFS without a custom one set).
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);
1208 return false;
1209 }
1210
1211 // Let the core know which version of the VFS we support.
1212 info->required_interface_version = 4;
1213 info->iface = &lr->vfs_interface;
1214 lr->core.usedVFS = true;
1215 return true;
1216 }
1217
1218 case 75:
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;
1229 return true;
1230 }
1231
1232 case 77:
1233 case RETRO_ENVIRONMENT_GET_DEVICE_POWER: {
1234 if (!data) return true;
1235 struct retro_device_power* power = (struct retro_device_power*)data;
1236 int percent;
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;
1241 break;
1242 case SDL_POWERSTATE_CHARGING:
1243 power->state = RETRO_POWERSTATE_CHARGING;
1244 break;
1245 case SDL_POWERSTATE_CHARGED:
1246 power->state = RETRO_POWERSTATE_CHARGED;
1247 break;
1248 case SDL_POWERSTATE_NO_BATTERY:
1249 power->state = RETRO_POWERSTATE_PLUGGED_IN;
1250 break;
1251 default:
1252 return false;
1253 }
1254 power->percent = (int8_t)percent;
1255 return true;
1256 }
1257
1258 case 81:
1259 case RETRO_ENVIRONMENT_GET_TARGET_SAMPLE_RATE: {
1260 if (!data) return true;
1261 SDL_AudioSpec spec;
1262 if (lr->core.audioStream && SDL_GetAudioStreamFormat(lr->core.audioStream, NULL, &spec) && spec.freq > 0) {
1263 // Active Device
1264 *(unsigned*)data = (unsigned)spec.freq;
1265 }
1266 else if (SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL) && spec.freq > 0) {
1267 // Default Device
1268 *(unsigned*)data = (unsigned)spec.freq;
1269 }
1270 else {
1271 // Desired Frequency
1272 *(unsigned*)data = SDL_LIBRETRO_AUDIO_DEFAULT_SAMPLE_RATE;
1273 }
1274 return true;
1275 }
1276
1277 case 46:
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;
1282 return true;
1283 }
1284
1285 // Anything else, including commands the library chooses not to
1286 // implement (camera, location, hardware rendering, achievements):
1287 // let the application implement the command through an event watch
1288 // on SDL_EVENT_LIBRETRO_ENVIRONMENT(cmd).
1289 default: {
1290 if (SDL_Libretro_PushEnvEvent(lr, cmd, data)) {
1291 return true;
1292 }
1293 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Unhandled environment callback: %u", cmd);
1294 return false;
1295 }
1296 }
1297}
1298
1299#endif /* SDL_LIBRETRO_ENV_IMPL_ONCE */
#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.