7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_CORE_IMPL_ONCE)
8#define SDL_LIBRETRO_CORE_IMPL_ONCE
15#ifndef SDL_LIBRETRO_GET_TICKS_NS
16#define SDL_LIBRETRO_GET_TICKS_NS() SDL_GetTicksNS()
24static void SDL_Libretro_ResetPortDevices(SDL_Libretro* lr) {
26 lr->core.portDeviceMap[i] = RETRO_DEVICE_JOYPAD;
36#define LOAD_SYM(sym) \
38 SDL_FunctionPointer fp = SDL_LoadFunction(lr->core.symbols.handle, #sym); \
39 SDL_memcpy(&lr->core.symbols.sym, &fp, sizeof(fp)); \
41 SDL_SetError("[SDL_Libretro] Failed to load symbol '%s'", #sym); \
42 SDL_UnloadObject(lr->core.symbols.handle); \
43 SDL_memset(&lr->core, 0, sizeof(lr->core)); \
44 SDL_Libretro_ResetPortDevices(lr); \
62static void SDL_Libretro_ReserveEvents(
void) {
63 static SDL_AtomicInt reserved;
64 if (!SDL_CompareAndSwapAtomicInt(&reserved, 0, 1)) {
70 const Uint32 next = SDL_RegisterEvents(1);
72 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Registered user events reach the SDL_EVENT_LIBRETRO range (0x%x-0x%x); they may collide", (
unsigned)
SDL_EVENT_LIBRETRO, (
unsigned)
SDL_EVENT_LIBRETRO_LAST);
76 const Uint32 base = SDL_RegisterEvents(count);
78 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Failed to reserve the SDL_EVENT_LIBRETRO range (0x%x-0x%x); registered user events may collide", (
unsigned)
SDL_EVENT_LIBRETRO, (
unsigned)
SDL_EVENT_LIBRETRO_LAST);
98 SDL_SetError(
"[SDL_Libretro] Failed to allocate context");
102 SDL_Libretro_ReserveEvents();
106 SDL_Libretro_ResetPortDevices(lr);
114 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_B] = SDL_SCANCODE_Z;
115 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_Y] = SDL_SCANCODE_A;
116 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_SELECT] = SDL_SCANCODE_RSHIFT;
117 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_START] = SDL_SCANCODE_RETURN;
118 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_UP] = SDL_SCANCODE_UP;
119 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_DOWN] = SDL_SCANCODE_DOWN;
120 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_LEFT] = SDL_SCANCODE_LEFT;
121 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_RIGHT] = SDL_SCANCODE_RIGHT;
122 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_A] = SDL_SCANCODE_X;
123 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_X] = SDL_SCANCODE_S;
124 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L] = SDL_SCANCODE_Q;
125 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R] = SDL_SCANCODE_W;
126 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L2] = SDL_SCANCODE_E;
127 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R2] = SDL_SCANCODE_R;
128 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L3] = SDL_SCANCODE_D;
129 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R3] = SDL_SCANCODE_F;
133 SDL_memcpy(lr->gamepadButtons[port], SDL_Libretro_DefaultGamepadButtons,
sizeof(lr->gamepadButtons[port]));
160 if (lr->gamepads[i]) {
161 SDL_CloseGamepad(lr->gamepads[i]);
162 lr->gamepads[i] = NULL;
166 SDL_Libretro_FreeCoreLibrary(lr);
167 SDL_Libretro_FreeMessages(lr);
168 SDL_Libretro_CloseConfig(lr);
184static const char* SDL_Libretro_GetCorePathFromName(
const SDL_Libretro* lr,
const char* coreName) {
185 if (!lr || !coreName || !lr->coreLibrary || SDL_strrchr(coreName,
'.') != NULL) {
188 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
189 if (lr->coreLibrary[i].corename && SDL_strcasecmp(lr->coreLibrary[i].corename, coreName) == 0) {
190 return lr->coreLibrary[i].path;
212 SDL_SetError(
"[SDL_Libretro] Invalid arguments");
216 if (SDL_Libretro_active && SDL_Libretro_active != lr) {
217 SDL_SetError(
"[SDL_Libretro] Another context already has a core loaded");
225 if (lr->saveDirectory[0] !=
'\0') {
226 SDL_CreateDirectory(lr->saveDirectory);
228 if (lr->systemDirectory[0] !=
'\0') {
229 SDL_CreateDirectory(lr->systemDirectory);
233 const char* path = SDL_Libretro_GetCorePathFromName(lr, core);
239 lr->core.symbols.handle = SDL_LoadObject(core);
240 if (!lr->core.symbols.handle) {
241 SDL_SetError(
"[SDL_Libretro] Failed to load core '%s': %s", core, SDL_GetError());
247 lr->core.apiVersion = lr->core.symbols.retro_api_version();
248 if (lr->core.apiVersion != 1) {
249 SDL_UnloadObject(lr->core.symbols.handle);
250 SDL_SetError(
"[SDL_Libretro] Unsupported Core API Version: %d", (
int)lr->core.apiVersion);
251 SDL_memset(&lr->core, 0,
sizeof(lr->core));
252 SDL_Libretro_ResetPortDevices(lr);
261 LOAD_SYM(retro_set_audio_sample_batch);
266 LOAD_SYM(retro_set_controller_port_device);
281 SDL_strlcpy(lr->core.corePath, core,
sizeof(lr->core.corePath));
285 lr->core.speed = 1.0f;
287 SDL_Libretro_active = lr;
290 lr->core.symbols.retro_set_environment(SDL_Libretro_EnvironmentCallback);
293 struct retro_system_info sysinfo = {0};
294 lr->core.symbols.retro_get_system_info(&sysinfo);
295 SDL_strlcpy(lr->core.libraryName, sysinfo.library_name ? sysinfo.library_name :
"",
sizeof(lr->core.libraryName));
296 SDL_strlcpy(lr->core.libraryVersion, sysinfo.library_version ? sysinfo.library_version :
"",
sizeof(lr->core.libraryVersion));
297 SDL_strlcpy(lr->core.validExtensions, sysinfo.valid_extensions ? sysinfo.valid_extensions :
"",
sizeof(lr->core.validExtensions));
298 lr->core.needFullpath = sysinfo.need_fullpath;
299 lr->core.blockExtract = sysinfo.block_extract;
302 SDL_strlcpy(lr->core.contentName, lr->core.libraryName,
sizeof(lr->core.contentName));
305 SDL_Libretro_LoadCoreConfig(lr);
308 lr->core.symbols.retro_init();
310 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Core loaded: %s %s", lr->core.libraryName, lr->core.libraryVersion);
334 SDL_Libretro_SaveCoreConfig(lr);
335 lr->core.symbols.retro_deinit();
336 SDL_UnloadObject(lr->core.symbols.handle);
340 SDL_Libretro_CloseSensors(lr);
341 SDL_Libretro_CloseMicrophone(lr);
342 SDL_Libretro_FreeCoreOptions(lr);
343 SDL_Libretro_FreeInputDescriptors(lr);
344 SDL_Libretro_FreeControllerInfo(lr);
345 SDL_Libretro_FreeMemoryMap(lr);
346 SDL_Libretro_FreeContentInfoOverrides(lr);
347 SDL_Libretro_FreeSubsystems(lr);
348 SDL_Libretro_StopRumble(lr);
350 SDL_memset(&lr->core, 0,
sizeof(lr->core));
351 SDL_Libretro_ResetPortDevices(lr);
352 if (SDL_Libretro_active == lr) {
353 SDL_Libretro_active = NULL;
355 lr->coreGeneration++;
357 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Core unloaded");
374 return lr && lr->core.symbols.handle != NULL;
389 if (!dst || dstSize == 0)
return 0;
394 const char* base = SDL_strrchr(path,
'/');
395 if (!base) base = SDL_strrchr(path,
'\\');
396 base = base ? base + 1 : path;
398 SDL_strlcpy(dst, base, dstSize);
400 if (!withExtension) {
401 char* dot = SDL_strrchr(dst,
'.');
402 if (dot) *dot =
'\0';
405 return SDL_strlen(dst);
412static const char* SDL_Libretro_GetExtension(
const char* path) {
413 const char* dot = SDL_strrchr(path,
'.');
414 return dot ? dot + 1 :
"";
429 if (!dst || dstSize == 0)
return 0;
431 if (!lr || lr->core.contentName[0] ==
'\0')
return 0;
437 if (lr->saveDirectory[0] !=
'\0') {
438 SDL_snprintf(dst, dstSize,
"%s/%s%s", lr->saveDirectory, lr->core.contentName, extension);
441 SDL_snprintf(dst, dstSize,
"%s%s", lr->core.contentName, extension);
444 return SDL_strlen(dst);
451static void SDL_Libretro_FreeContentInfoOverrides(SDL_Libretro* lr) {
452 if (lr->core.contentInfoOverrides) {
453 for (
unsigned i = 0; i < lr->core.contentInfoOverrideCount; i++) {
454 SDL_free((
void*)lr->core.contentInfoOverrides[i].extensions);
456 SDL_free(lr->core.contentInfoOverrides);
457 lr->core.contentInfoOverrides = NULL;
459 lr->core.contentInfoOverrideCount = 0;
465static void SDL_Libretro_FreeSubsystems(SDL_Libretro* lr) {
466 if (!lr || !lr->core.subsystems)
return;
467 for (
unsigned i = 0; i < lr->core.subsystemCount; i++) {
469 SDL_free((
void*)lr->core.subsystems[i].desc);
470 SDL_free((
void*)lr->core.subsystems[i].ident);
473 if (lr->core.subsystems[i].roms) {
474 for (
unsigned r = 0; r < lr->core.subsystems[i].num_roms; r++) {
475 SDL_free((
void*)lr->core.subsystems[i].roms[r].desc);
476 SDL_free((
void*)lr->core.subsystems[i].roms[r].valid_extensions);
479 if (lr->core.subsystems[i].roms[r].memory) {
480 for (
unsigned m = 0; m < lr->core.subsystems[i].roms[r].num_memory; m++) {
481 SDL_free((
void*)lr->core.subsystems[i].roms[r].memory[m].extension);
483 SDL_free((
void*)lr->core.subsystems[i].roms[r].memory);
486 SDL_free((
void*)lr->core.subsystems[i].roms);
489 SDL_free(lr->core.subsystems);
490 lr->core.subsystems = NULL;
491 lr->core.subsystemCount = 0;
497static void SDL_Libretro_FreeInputDescriptors(SDL_Libretro* lr) {
498 if (!lr || !lr->core.inputDescriptors)
return;
499 for (
unsigned i = 0; i < lr->core.inputDescriptorsCount; i++) {
500 SDL_free((
void*)lr->core.inputDescriptors[i].description);
502 SDL_free(lr->core.inputDescriptors);
503 lr->core.inputDescriptors = NULL;
504 lr->core.inputDescriptorsCount = 0;
510static void SDL_Libretro_FreeControllerInfo(SDL_Libretro* lr) {
511 if (!lr || !lr->core.controllerInfo)
return;
512 for (
unsigned i = 0; i < lr->core.controllerInfoCount; i++) {
513 if (lr->core.controllerInfo[i].types) {
514 for (
unsigned t = 0; t < lr->core.controllerInfo[i].num_types; t++) {
515 SDL_free((
void*)lr->core.controllerInfo[i].types[t].desc);
517 SDL_free((
void*)lr->core.controllerInfo[i].types);
520 SDL_free(lr->core.controllerInfo);
521 lr->core.controllerInfo = NULL;
522 lr->core.controllerInfoCount = 0;
528static const struct retro_subsystem_info* SDL_Libretro_GetSubsystemById(
const SDL_Libretro* lr,
unsigned subsystemId) {
529 if (!lr)
return NULL;
530 for (
unsigned i = 0; i < lr->core.subsystemCount; i++) {
531 if (lr->core.subsystems[i].id == subsystemId) {
532 return &lr->core.subsystems[i];
541static const struct retro_subsystem_info* SDL_Libretro_GetSubsystemByName(
const SDL_Libretro* lr,
const char* name) {
542 if (!lr || !name)
return NULL;
543 for (
unsigned i = 0; i < lr->core.subsystemCount; i++) {
544 const struct retro_subsystem_info* subsys = &lr->core.subsystems[i];
545 if ((subsys->ident && SDL_strcasecmp(subsys->ident, name) == 0) ||
546 (subsys->desc && SDL_strcasecmp(subsys->desc, name) == 0)) {
561static int SDL_Libretro_GetContentInfoOverride(
const SDL_Libretro* lr,
const char* ext) {
562 if (!lr || !ext || ext[0] ==
'\0')
return -1;
563 for (
unsigned i = 0; i < lr->core.contentInfoOverrideCount; i++) {
564 if (SDL_Libretro_ExtensionInList(ext, lr->core.contentInfoOverrides[i].extensions)) {
575static bool SDL_Libretro_ContentNeedsFullpath(
const SDL_Libretro* lr,
const char* ext) {
576 int i = SDL_Libretro_GetContentInfoOverride(lr, ext);
577 return i >= 0 ? lr->core.contentInfoOverrides[i].need_fullpath : lr->core.needFullpath;
584static bool SDL_Libretro_ContentPersistData(
const SDL_Libretro* lr,
const char* ext) {
585 int i = SDL_Libretro_GetContentInfoOverride(lr, ext);
586 return i >= 0 ? lr->core.contentInfoOverrides[i].persistent_data :
false;
593static void SDL_Libretro_FreeGameInfoExtArray(SDL_Libretro* lr) {
594 for (
unsigned i = 0; i < lr->core.gameInfoExtCount; i++) {
595 struct retro_game_info_ext* entry = &lr->core.gameInfoExtArray[i];
596 SDL_free((
void*)entry->full_path);
597 SDL_free((
void*)entry->dir);
598 SDL_free((
void*)entry->name);
599 SDL_free((
void*)entry->ext);
601 SDL_free(lr->core.gameInfoExtArray);
602 lr->core.gameInfoExtArray = NULL;
603 lr->core.gameInfoExtCount = 0;
610static void SDL_Libretro_FillGameInfoExt(
struct retro_game_info_ext* entry,
const char* path) {
612 entry->full_path = SDL_strdup(path);
614 char buffer[SDL_LIBRETRO_MAX_PATH];
616 entry->name = SDL_strdup(buffer);
619 const char* dot = SDL_strrchr(buffer,
'.');
620 char* ext = SDL_strdup(dot ? dot + 1 :
"");
622 for (
char* c = ext; *c; c++) *c = (
char)SDL_tolower((
unsigned char)*c);
626 SDL_strlcpy(buffer, path,
sizeof(buffer));
627 char* sep = SDL_strrchr(buffer,
'/');
628 char* backslash = SDL_strrchr(buffer,
'\\');
629 if (!sep || (backslash && backslash > sep)) sep = backslash;
634 entry->dir = SDL_strdup(buffer);
643static void SDL_Libretro_ResetContentState(SDL_Libretro* lr) {
644 lr->core.contentPath[0] =
'\0';
645 lr->core.contentDir[0] =
'\0';
646 lr->core.contentExt[0] =
'\0';
647 SDL_strlcpy(lr->core.contentName, lr->core.libraryName,
sizeof(lr->core.contentName));
650 if (lr->core.contentTempPath[0] !=
'\0') {
651 SDL_RemovePath(lr->core.contentTempPath);
652 lr->core.contentTempPath[0] =
'\0';
656 if (lr->core.contentTempDir[0] !=
'\0') {
657 SDL_Libretro_RemoveTree(lr->core.contentTempDir);
658 lr->core.contentTempDir[0] =
'\0';
661 if (lr->core.gameInfoExt.persistent_data) {
662 SDL_free((
void*)lr->core.gameInfoExt.data);
664 SDL_memset(&lr->core.gameInfoExt, 0,
sizeof(lr->core.gameInfoExt));
665 SDL_Libretro_FreeGameInfoExtArray(lr);
668 SDL_Libretro_FreeDiskImages(lr);
671 lr->core.runloop_frame_time_last = 0;
695static const char* SDL_Libretro_SetContentIdentity(SDL_Libretro* lr,
const char* path,
const char* identityPath) {
696 SDL_strlcpy(lr->core.contentPath, path,
sizeof(lr->core.contentPath));
697 if (!identityPath) identityPath = path;
702 SDL_strlcpy(lr->core.contentExt, SDL_Libretro_GetExtension(identityPath),
sizeof(lr->core.contentExt));
703 for (
char* c = lr->core.contentExt; *c; c++)
704 *c = (
char)SDL_tolower((
unsigned char)*c);
707 SDL_strlcpy(lr->core.contentDir, identityPath,
sizeof(lr->core.contentDir));
708 char* sep = SDL_strrchr(lr->core.contentDir,
'/');
709 if (!sep) sep = SDL_strrchr(lr->core.contentDir,
'\\');
713 lr->core.contentDir[0] =
'\0';
716 lr->core.gameInfoExt.full_path = lr->core.contentPath;
717 lr->core.gameInfoExt.dir = lr->core.contentDir;
718 lr->core.gameInfoExt.name = lr->core.contentName;
719 lr->core.gameInfoExt.ext = lr->core.contentExt;
733static bool SDL_Libretro_LoadCoreForGame(SDL_Libretro* lr,
const char* gamePath) {
734 if (!lr || !gamePath) {
735 SDL_SetError(
"[SDL_Libretro] No core loaded, and no content to pick one by");
740 const char* extension = SDL_Libretro_GetExtension(gamePath);
741 if (extension[0] ==
'\0') {
742 SDL_SetError(
"[SDL_Libretro] Game path '%s' has no file extension", gamePath);
747 bool claimed =
false;
748 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
750 if (!SDL_Libretro_ExtensionInList(extension, lr->coreLibrary[i].supported_extensions)) {
763 SDL_SetError(
"[SDL_Libretro] No core found for extension '%s'", extension);
774static void SDL_Libretro_SetCoreCallbacks(SDL_Libretro* lr) {
776 if (lr->core.symbols.retro_set_video_refresh) lr->core.symbols.retro_set_video_refresh(SDL_Libretro_VideoRefresh);
777 if (lr->core.symbols.retro_set_audio_sample) lr->core.symbols.retro_set_audio_sample(SDL_Libretro_AudioSample);
778 if (lr->core.symbols.retro_set_audio_sample_batch) lr->core.symbols.retro_set_audio_sample_batch(SDL_Libretro_AudioSampleBatch);
779 if (lr->core.symbols.retro_set_input_poll) lr->core.symbols.retro_set_input_poll(SDL_Libretro_InputPoll);
780 if (lr->core.symbols.retro_set_input_state) lr->core.symbols.retro_set_input_state(SDL_Libretro_InputState);
789static bool SDL_Libretro_FinishGameLoad(SDL_Libretro* lr) {
790 lr->core.gameLoaded =
true;
794 if (lr->core.portDeviceMap[port] != RETRO_DEVICE_JOYPAD) {
795 lr->core.symbols.retro_set_controller_port_device(port, lr->core.portDeviceMap[port]);
800 SDL_Libretro_AutoLoadSRAM(lr);
803 struct retro_system_av_info avInfo = {0};
804 lr->core.symbols.retro_get_system_av_info(&avInfo);
805 lr->core.width = avInfo.geometry.base_width;
806 lr->core.height = avInfo.geometry.base_height;
807 lr->core.fps = avInfo.timing.fps;
808 lr->core.sampleRate = avInfo.timing.sample_rate;
809 lr->core.aspectRatio = avInfo.geometry.aspect_ratio;
813 if (lr->renderer && !SDL_Libretro_InitVideo(lr)) {
814 SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO,
"[SDL_Libretro] Video failed to initialize: %s", SDL_GetError());
819 if (!SDL_Libretro_InitAudio(lr)) {
820 SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Audio failed to initialize: %s", SDL_GetError());
823 SDL_Log(
"[SDL_Libretro] Game loaded: %s [%ux%u @ %.2ffps]", lr->core.contentName, lr->core.width, lr->core.height, lr->core.fps);
827 if (lr->rewindEnabled && !lr->rewindReference && lr->core.symbols.retro_serialize_size() > 0) {
828 SDL_Libretro_RewindAllocate(lr);
849static bool SDL_Libretro_ContentExists(
const SDL_Libretro* lr,
const char* path) {
851 if (lr->vfs_interface.stat) {
852 return (lr->vfs_interface.stat(path, NULL) & RETRO_VFS_STAT_IS_VALID) != 0;
854 return SDL_GetPathInfo(path, NULL);
857static bool SDL_Libretro_LoadGameCommon(SDL_Libretro* lr,
const char* gamePath,
const char* identityPath,
void* fileData,
size_t fileSize) {
864 SDL_SetError(
"[SDL_Libretro] This core requires content");
870 if (gamePath && !fileData) {
871 if (!SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(identityPath ? identityPath : gamePath))) {
872 fileData = SDL_LoadFile(gamePath, &fileSize);
874 SDL_SetError(
"[SDL_Libretro] Failed to load game file '%s'", gamePath);
878 else if (!SDL_Libretro_ContentExists(lr, gamePath)) {
879 SDL_SetError(
"[SDL_Libretro] Game file '%s' not found", gamePath);
887 struct retro_game_info gameInfo = {0};
888 bool persistData =
false;
891 SDL_memset(&lr->core.gameInfoExt, 0,
sizeof(lr->core.gameInfoExt));
894 const char* ext = SDL_Libretro_SetContentIdentity(lr, gamePath, identityPath);
896 bool needFullpath = SDL_Libretro_ContentNeedsFullpath(lr, ext);
897 persistData = SDL_Libretro_ContentPersistData(lr, ext);
899 gameInfo.path = lr->core.contentPath;
903 fileData = SDL_LoadFile(gamePath, &fileSize);
905 SDL_SetError(
"[SDL_Libretro] Failed to load game file '%s'", gamePath);
906 SDL_Libretro_ResetContentState(lr);
910 gameInfo.data = fileData;
911 gameInfo.size = fileSize;
920 lr->core.gameInfoExt.data = gameInfo.data;
921 lr->core.gameInfoExt.size = gameInfo.size;
922 lr->core.gameInfoExt.persistent_data = persistData;
929 SDL_Libretro_SetCoreCallbacks(lr);
931 bool result = lr->core.symbols.retro_load_game(gamePath ? &gameInfo : NULL);
934 if (fileData && !(persistData && result)) {
936 lr->core.gameInfoExt.data = NULL;
937 lr->core.gameInfoExt.size = 0;
941 SDL_Libretro_ResetContentState(lr);
942 SDL_SetError(
"[SDL_Libretro] Core failed to load the game");
946 SDL_Libretro_FinishGameLoad(lr);
957static bool SDL_Libretro_LoadGameFile(SDL_Libretro* lr,
const char* gamePath) {
963 return SDL_Libretro_LoadGameCommon(lr, gamePath, NULL, NULL, 0);
988 if (!lr)
return false;
990#if defined(SDL_LIBRETRO_ENABLE_PHYSFS) && !defined(SDL_LIBRETRO_DISABLE_PHYSFS)
993 if (gamePath && SDL_strcasecmp(SDL_Libretro_GetExtension(gamePath),
"zip") == 0) {
998 return SDL_Libretro_LoadGameFile(lr, gamePath);
1010static bool SDL_Libretro_GetTempDirectory(SDL_Libretro* lr,
char* dir,
size_t dirSize) {
1012 if (saveDir && saveDir[0]) {
1013 SDL_snprintf(dir, dirSize,
"%s/tmp/", saveDir);
1015 else if (SDL_Libretro_GetPrefDirectory(dir, dirSize)) {
1016 SDL_strlcat(dir,
"/tmp/", dirSize);
1019 const char* base = SDL_GetBasePath();
1020 SDL_snprintf(dir, dirSize,
"%stmp/", base ? base :
"./");
1022 if (!SDL_CreateDirectory(dir)) {
1023 return SDL_SetError(
"[SDL_Libretro] Failed to create temp directory '%s': %s", dir, SDL_GetError());
1032static SDL_EnumerationResult SDLCALL SDL_Libretro_RemoveTreeEntry(
void* userdata,
const char* dirname,
const char* fname) {
1034 char path[SDL_LIBRETRO_MAX_PATH];
1035 size_t len = SDL_strlen(dirname);
1036 bool slash = len > 0 && (dirname[len - 1] ==
'/' || dirname[len - 1] ==
'\\');
1037 SDL_snprintf(path,
sizeof(path),
"%s%s%s", dirname, slash ?
"" :
"/", fname);
1039 if (SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY) {
1040 SDL_EnumerateDirectory(path, SDL_Libretro_RemoveTreeEntry, NULL);
1042 SDL_RemovePath(path);
1043 return SDL_ENUM_CONTINUE;
1050static void SDL_Libretro_RemoveTree(
const char* path) {
1051 SDL_EnumerateDirectory(path, SDL_Libretro_RemoveTreeEntry, NULL);
1052 SDL_RemovePath(path);
1072static bool SDL_Libretro_SpillToTempFile(SDL_Libretro* lr, SDL_IOStream* src,
const char* path,
bool closeio,
char* tempPath,
size_t tempPathSize) {
1074 size_t fileSize = 0;
1075 void* fileData = SDL_LoadFile_IO(src, &fileSize, closeio);
1076 if (!fileData)
return false;
1078 char name[SDL_LIBRETRO_MAX_PATH];
1080 SDL_strlcpy(name,
"content.bin",
sizeof(name));
1084 char dir[SDL_LIBRETRO_MAX_PATH];
1085 if (!SDL_Libretro_GetTempDirectory(lr, dir,
sizeof(dir))) {
1091 SDL_snprintf(tempPath, tempPathSize,
"%s%s", dir, name);
1092 const char* dot = SDL_strrchr(name,
'.');
1093 int stemLen = dot ? (int)(dot - name) : (int)SDL_strlen(name);
1094 for (
int attempt = 1; SDL_GetPathInfo(tempPath, NULL); attempt++) {
1096 SDL_SetError(
"[SDL_Libretro] Could not find a free file name for '%s' in '%s'", name, dir);
1100 SDL_snprintf(tempPath, tempPathSize,
"%s%.*s-%d%s", dir, stemLen, name, attempt, dot ? dot :
"");
1103 bool saved = SDL_SaveFile(tempPath, fileData, fileSize);
1106 SDL_SetError(
"[SDL_Libretro] Failed to save content to '%s'", tempPath);
1132 if (!lr || !src || !path) {
1133 if (src && closeio) SDL_CloseIO(src);
1134 return SDL_InvalidParamError(
"src, path");
1139 if (closeio) SDL_CloseIO(src);
1144 if (!SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(path))) {
1146 size_t fileSize = 0;
1147 void* fileData = SDL_LoadFile_IO(src, &fileSize, closeio);
1148 if (!fileData)
return false;
1149 return SDL_Libretro_LoadGameCommon(lr, path, NULL, fileData, fileSize);
1155 char tempPath[SDL_LIBRETRO_MAX_PATH];
1156 if (!SDL_Libretro_SpillToTempFile(lr, src, path, closeio, tempPath,
sizeof(tempPath))) {
1162 bool result = SDL_Libretro_LoadGameCommon(lr, tempPath, path, NULL, 0);
1164 SDL_strlcpy(lr->core.contentTempPath, tempPath,
sizeof(lr->core.contentTempPath));
1167 SDL_RemovePath(tempPath);
1191 SDL_SetError(
"[SDL_Libretro] Invalid arguments for LoadGameSpecial");
1196 const struct retro_subsystem_info* subsys = SDL_Libretro_GetSubsystemById(lr, subsystemId);
1198 SDL_SetError(
"[SDL_Libretro] Unknown subsystem id %u", subsystemId);
1204 unsigned requiredCount = 0;
1205 bool missingRequired =
false;
1206 for (
unsigned i = 0; i < subsys->num_roms; i++) {
1207 if (subsys->roms[i].required) {
1209 missingRequired = missingRequired || i >= numPaths || !paths[i];
1212 if (numPaths > subsys->num_roms || missingRequired) {
1213 SDL_SetError(
"[SDL_Libretro] Subsystem '%s' expects %u ROM(s) (%u required), got %u", subsys->desc ? subsys->desc : (subsys->ident ? subsys->ident :
""), subsys->num_roms, requiredCount, numPaths);
1219 struct retro_game_info* gameInfos = (
struct retro_game_info*)SDL_calloc(numPaths,
sizeof(
struct retro_game_info));
1220 void** fileBuffers = (
void**)SDL_calloc(numPaths,
sizeof(
void*));
1221 if (!gameInfos || !fileBuffers) {
1222 SDL_free(gameInfos);
1223 SDL_free(fileBuffers);
1224 SDL_SetError(
"[SDL_Libretro] Allocation failed");
1228 for (
unsigned i = 0; i < numPaths; i++) {
1229 if (!paths[i])
continue;
1230 gameInfos[i].path = paths[i];
1232 bool needFullpath = lr->core.needFullpath;
1233 if (i < subsys->num_roms) {
1234 needFullpath = subsys->roms[i].need_fullpath;
1237 if (!needFullpath) {
1238 size_t fileSize = 0;
1239 fileBuffers[i] = SDL_LoadFile(paths[i], &fileSize);
1240 if (!fileBuffers[i]) {
1241 SDL_SetError(
"[SDL_Libretro] Failed to load file '%s'", paths[i]);
1242 for (
unsigned j = 0; j < i; j++) SDL_free(fileBuffers[j]);
1243 SDL_free(fileBuffers);
1244 SDL_free(gameInfos);
1247 gameInfos[i].data = fileBuffers[i];
1248 gameInfos[i].size = fileSize;
1250 else if (!SDL_Libretro_ContentExists(lr, paths[i])) {
1251 SDL_SetError(
"[SDL_Libretro] File '%s' not found", paths[i]);
1252 for (
unsigned j = 0; j < i; j++) SDL_free(fileBuffers[j]);
1253 SDL_free(fileBuffers);
1254 SDL_free(gameInfos);
1263 SDL_memset(&lr->core.gameInfoExt, 0,
sizeof(lr->core.gameInfoExt));
1265 SDL_Libretro_SetContentIdentity(lr, paths[0], NULL);
1266 lr->core.gameInfoExt.data = gameInfos[0].data;
1267 lr->core.gameInfoExt.size = gameInfos[0].size;
1268 lr->core.gameInfoExt.persistent_data =
false;
1271 SDL_strlcpy(lr->core.contentName, lr->core.libraryName,
sizeof(lr->core.contentName));
1275 SDL_Libretro_FreeGameInfoExtArray(lr);
1276 lr->core.gameInfoExtArray = (
struct retro_game_info_ext*)SDL_calloc(numPaths,
sizeof(
struct retro_game_info_ext));
1277 if (lr->core.gameInfoExtArray) {
1278 lr->core.gameInfoExtCount = numPaths;
1279 for (
unsigned i = 0; i < numPaths; i++) {
1280 SDL_Libretro_FillGameInfoExt(&lr->core.gameInfoExtArray[i], paths[i]);
1281 lr->core.gameInfoExtArray[i].data = gameInfos[i].data;
1282 lr->core.gameInfoExtArray[i].size = gameInfos[i].size;
1283 lr->core.gameInfoExtArray[i].persistent_data =
false;
1287 SDL_Libretro_SetCoreCallbacks(lr);
1289 bool result = lr->core.symbols.retro_load_game_special(subsystemId, gameInfos, numPaths);
1293 for (
unsigned i = 0; i < numPaths; i++) SDL_free(fileBuffers[i]);
1294 SDL_free(fileBuffers);
1295 SDL_free(gameInfos);
1296 lr->core.gameInfoExt.data = NULL;
1297 lr->core.gameInfoExt.size = 0;
1298 for (
unsigned i = 0; i < lr->core.gameInfoExtCount; i++) {
1299 lr->core.gameInfoExtArray[i].data = NULL;
1300 lr->core.gameInfoExtArray[i].size = 0;
1304 SDL_Libretro_ResetContentState(lr);
1305 SDL_SetError(
"[SDL_Libretro] Core failed to load the special game");
1309 SDL_Libretro_FinishGameLoad(lr);
1332 const struct retro_subsystem_info* subsys = SDL_Libretro_GetSubsystemByName(lr, subsystem);
1334 SDL_SetError(
"[SDL_Libretro] Unknown subsystem '%s'", subsystem ? subsystem :
"(null)");
1350 if (!lr || !lr->core.gameLoaded)
return;
1353 SDL_Libretro_AutoSaveSRAM(lr);
1356 lr->core.symbols.retro_unload_game();
1357 lr->core.gameLoaded =
false;
1359 lr->core.shutdown =
false;
1360 SDL_Libretro_ResetContentState(lr);
1361 SDL_Libretro_StopRumble(lr);
1363 SDL_Libretro_RewindFree(lr);
1364 SDL_Libretro_CloseAudio(lr);
1365 SDL_Libretro_CloseVideo(lr);
1366 SDL_Libretro_PhysFS_ClearMount(lr);
1367 SDL_Log(
"[SDL_Libretro] Game unloaded");
1384 return lr && lr->core.gameLoaded;
1401 return lr && !lr->core.supportNoGame;
1413 if (!lr || !lr->core.gameLoaded) {
1414 SDL_SetError(
"[SDL_Libretro] No game loaded");
1417 lr->core.symbols.retro_reset();
1420 lr->core.runloop_frame_time_last = 0;
1423 SDL_Libretro_ClearRewind(lr);
1426 if (lr->core.audioStream) {
1427 SDL_ClearAudioStream(lr->core.audioStream);
1428 lr->core.singleSampleCount = 0;
1429 lr->core.drcDriftAvg = 0.0;
1441#define SDL_LIBRETRO_FRAME_TIME_MAX_GAP_NS (250 * SDL_NS_PER_MS)
1448static retro_usec_t SDL_Libretro_FrameTimeDelta(SDL_Libretro* lr, retro_usec_t referenceUsec,
int ticks) {
1450 Uint64 lastNS = lr->core.runloop_frame_time_last;
1451 lr->core.runloop_frame_time_last = nowNS;
1452 if (!lastNS || referenceUsec <= 0 || nowNS - lastNS > SDL_LIBRETRO_FRAME_TIME_MAX_GAP_NS) {
1453 return lr->core.runloop_frame_time.reference;
1455 if (lr->core.speed != 1.0f) {
1456 return referenceUsec;
1458 retro_usec_t measured = (retro_usec_t)((nowNS - lastNS) / SDL_NS_PER_US);
1459 return ticks > 1 ? measured / ticks : measured;
1467static void SDL_Libretro_Tick(SDL_Libretro* lr, retro_usec_t frameTimeUsec) {
1468 if (lr->core.runloop_frame_time.callback) {
1469 lr->core.runloop_frame_time.callback(frameTimeUsec);
1473 SDL_Libretro_ReportAudioBufferStatus(lr);
1476 lr->core.symbols.retro_run();
1479 SDL_Libretro_RewindCapture(lr);
1482 if (lr->core.audioStream && lr->core.audio_callback.callback) {
1483 lr->core.audio_callback.callback();
1503 if (!lr || !lr->core.gameLoaded || lr->core.shutdown)
return;
1506 if (lr->core.videoReinitPending) {
1507 lr->core.videoReinitPending =
false;
1508 SDL_Libretro_InitVideo(lr);
1512 if (lr->core.audioReinitPending) {
1513 lr->core.audioReinitPending =
false;
1514 SDL_Libretro_InitAudio(lr);
1518 if (lr->core.speed == 0.0f) {
1519 lr->core.runloop_frame_time_last = 0;
1525 if (lr->core.rumbleStrong[port] > 0.0f || lr->core.rumbleWeak[port] > 0.0f) {
1526 SDL_Libretro_ApplyRumble(lr, port);
1531 if (lr->rewindEnabled && lr->core.speed < 0.0f) {
1533 if (lr->core.lastTickNS == 0) {
1534 lr->core.lastTickNS = nowNS;
1536 double frameTime = (double)(nowNS - lr->core.lastTickNS) / 1.0e9;
1537 lr->core.lastTickNS = nowNS;
1538 double framePeriod = (lr->core.fps > 0.0) ? (1.0 / lr->core.fps) : (1.0 / 60.0);
1540 unsigned interval = lr->rewindCaptureInterval > 0 ? lr->rewindCaptureInterval : 1;
1541 double stepPeriod = framePeriod * (double)interval;
1542 lr->core.speedAccumulator += frameTime * (double)(-lr->core.speed);
1545 int maxTicks = (int)(-lr->core.speed + 1.0f);
1546 if (maxTicks < 1) maxTicks = 1;
1549 double maxAccumulator = stepPeriod * (double)maxTicks;
1550 if (lr->core.speedAccumulator > maxAccumulator) {
1551 lr->core.speedAccumulator = maxAccumulator;
1555 lr->rewindActive =
true;
1556 while (lr->core.speedAccumulator >= stepPeriod && maxTicks-- > 0 && !lr->core.shutdown) {
1557 lr->core.speedAccumulator -= stepPeriod;
1558 if (!SDL_Libretro_RewindStepState(lr))
break;
1559 lr->core.symbols.retro_run();
1561 lr->rewindActive =
false;
1563 lr->core.runloop_frame_time_last = 0;
1568 SDL_Libretro_UpdateDRC(lr, lr->core.speed);
1572 if (lr->core.lastTickNS == 0) {
1574 lr->core.lastTickNS = nowNS;
1575 lr->core.speedAccumulator = 0.0;
1576 SDL_Libretro_Tick(lr, SDL_Libretro_FrameTimeDelta(lr, 0, 1));
1581 double frameTime = (double)(nowNS - lr->core.lastTickNS) / 1.0e9;
1582 lr->core.lastTickNS = nowNS;
1585 double framePeriod = (lr->core.fps > 0.0) ? (1.0 / lr->core.fps) : (1.0 / 60.0);
1588 retro_usec_t referenceUsec = (retro_usec_t)(framePeriod * 1.0e6 / (
double)lr->core.speed);
1591 double cadence = (framePeriod > 0.0) ? (frameTime / framePeriod) : 0.0;
1592 if (lr->core.speed == 1.0f && cadence > 0.9 && cadence < 1.1) {
1593 lr->core.speedAccumulator = 0.0;
1594 SDL_Libretro_Tick(lr, SDL_Libretro_FrameTimeDelta(lr, referenceUsec, 1));
1598 lr->core.speedAccumulator += frameTime * (double)lr->core.speed;
1601 int maxTicks = (int)(lr->core.speed + 1.0f);
1602 if (maxTicks < 1) maxTicks = 1;
1605 double maxAccumulator = framePeriod * (double)maxTicks;
1606 if (lr->core.speedAccumulator > maxAccumulator) {
1607 lr->core.speedAccumulator = maxAccumulator;
1612 while (lr->core.speedAccumulator >= framePeriod && ticks < maxTicks) {
1613 lr->core.speedAccumulator -= framePeriod;
1616 if (ticks == 0)
return;
1619 retro_usec_t frameTimeUsec = SDL_Libretro_FrameTimeDelta(lr, referenceUsec, ticks);
1620 for (
int i = 0; i < ticks && !lr->core.shutdown; i++) {
1621 SDL_Libretro_Tick(lr, frameTimeUsec);
1637 return lr && lr->core.shutdown;
1656static void SDL_Libretro_PushEvent(SDL_Libretro* lr, Uint32 type,
void* data) {
1659 event.user.type = type;
1660 event.user.data1 = lr;
1661 event.user.data2 = data;
1662 SDL_PushEvent(&event);
1671static bool SDLCALL SDL_Libretro_KeepUnclaimedEnvEvent(
void* userdata, SDL_Event* event) {
1672 const SDL_UserEvent* pushed = (
const SDL_UserEvent*)userdata;
1673 return !(
event->type == pushed->type &&
event->user.data1 == pushed->data1 &&
event->user.reserved == pushed->reserved);
1692static bool SDL_Libretro_PushEnvEvent(SDL_Libretro* lr,
unsigned cmd,
void* data) {
1695 unsigned flags = cmd & (unsigned)(RETRO_ENVIRONMENT_EXPERIMENTAL | RETRO_ENVIRONMENT_PRIVATE);
1696 if ((cmd & ~flags) >= 0x400 || flags == (
unsigned)(RETRO_ENVIRONMENT_EXPERIMENTAL | RETRO_ENVIRONMENT_PRIVATE)) {
1700 static SDL_AtomicInt sequence;
1704 event.user.reserved = (Uint32)SDL_AddAtomicInt(&sequence, 1) + 1;
1705 event.user.data1 = lr;
1706 event.user.data2 = data;
1707 const SDL_UserEvent pushed =
event.user;
1711 const bool queued = SDL_PushEvent(&event);
1712 if (event.user.code == 0) {
1720 SDL_FilterEvents(SDL_Libretro_KeepUnclaimedEnvEvent, (
void*)&pushed);
1727static void SDL_Libretro_FreeCoreLibrary(SDL_Libretro* lr) {
1728 if (lr->coreLibrary) {
1729 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
1730 SDL_free(lr->coreLibrary[i].corename);
1731 SDL_free(lr->coreLibrary[i].supported_extensions);
1732 SDL_free(lr->coreLibrary[i].path);
1734 SDL_free(lr->coreLibrary);
1735 lr->coreLibrary = NULL;
1737 lr->coreLibraryCount = 0;
1741#if defined(SDL_PLATFORM_WINDOWS)
1742#define SDL_LIBRETRO_CORE_EXTENSION ".dll"
1743#elif defined(SDL_PLATFORM_APPLE)
1744#define SDL_LIBRETRO_CORE_EXTENSION ".dylib"
1745#elif defined(SDL_PLATFORM_EMSCRIPTEN)
1746#define SDL_LIBRETRO_CORE_EXTENSION ".wasm"
1748#define SDL_LIBRETRO_CORE_EXTENSION ".so"
1751static SDL_EnumerationResult SDLCALL SDL_Libretro_SetCoreDirectory_Iterator(
void* userdata,
const char* dirname,
const char* fname) {
1755 const char* dot = SDL_strrchr(fname,
'.');
1756 if (!dot || SDL_strcasecmp(dot,
".info") != 0) {
1757 return SDL_ENUM_CONTINUE;
1761 char infoPath[SDL_LIBRETRO_MAX_PATH];
1762 SDL_snprintf(infoPath,
sizeof(infoPath),
"%s%s", dirname, fname);
1765 SDL_ini* ini = INI_Load(infoPath);
1767 return SDL_ENUM_CONTINUE;
1771 const char* corename = INI_GetString(ini, NULL,
"corename", NULL);
1774 return SDL_ENUM_CONTINUE;
1778 SDL_Libretro_CoreInfo* grown = (SDL_Libretro_CoreInfo*)SDL_realloc(
1780 (lr->coreLibraryCount + 1) *
sizeof(SDL_Libretro_CoreInfo));
1783 SDL_SetError(
"[SDL_Libretro] Out of memory building the core library");
1784 return SDL_ENUM_FAILURE;
1786 lr->coreLibrary = grown;
1789 SDL_Libretro_CoreInfo* entry = &lr->coreLibrary[lr->coreLibraryCount];
1790 const char* extensions = INI_GetString(ini, NULL,
"supported_extensions", NULL);
1791 entry->corename = SDL_strdup(corename);
1792 entry->supported_extensions = extensions ? SDL_strdup(extensions) : NULL;
1793 entry->needs_fullpath = INI_GetBoolean(ini, NULL,
"needs_fullpath",
false);
1794 entry->supports_no_game = INI_GetBoolean(ini, NULL,
"supports_no_game",
false);
1795 entry->block_extract = INI_GetBoolean(ini, NULL,
"block_extract",
false);
1798 char base[SDL_LIBRETRO_MAX_PATH];
1799 size_t baseLen = (size_t)(dot - fname);
1800 SDL_strlcpy(base, fname, (baseLen <
sizeof(base)) ? baseLen + 1 : sizeof(base));
1801 char corePath[SDL_LIBRETRO_MAX_PATH];
1802 SDL_snprintf(corePath,
sizeof(corePath),
"%s%s%s", dirname, base, SDL_LIBRETRO_CORE_EXTENSION);
1803 entry->path = SDL_strdup(corePath);
1808 if (!entry->corename || !entry->path || (extensions && !entry->supported_extensions)) {
1809 SDL_free(entry->corename);
1810 SDL_free(entry->supported_extensions);
1811 SDL_free(entry->path);
1812 SDL_SetError(
"[SDL_Libretro] Out of memory building the core library");
1813 return SDL_ENUM_FAILURE;
1815 lr->coreLibraryCount++;
1816 return SDL_ENUM_CONTINUE;
1824static int SDLCALL SDL_Libretro_CompareCoreInfo(
const void* a,
const void* b) {
1825 const SDL_Libretro_CoreInfo* left = (
const SDL_Libretro_CoreInfo*)a;
1826 const SDL_Libretro_CoreInfo* right = (
const SDL_Libretro_CoreInfo*)b;
1827 int result = SDL_strcasecmp(left->corename, right->corename);
1828 return result != 0 ? result : SDL_strcmp(left->path, right->path);
1845 if (!lr)
return false;
1846 const char* newPath = path ? path :
"";
1847 SDL_strlcpy(lr->coreDirectory, newPath,
sizeof(lr->coreDirectory));
1850 SDL_Libretro_FreeCoreLibrary(lr);
1851 if (lr->coreDirectory[0] ==
'\0')
return true;
1854 if (!SDL_GetPathInfo(lr->coreDirectory, &info) || info.type != SDL_PATHTYPE_DIRECTORY) {
1855 SDL_SetError(
"[SDL_Libretro] Core directory '%s' is not a directory", lr->coreDirectory);
1859 if (!SDL_EnumerateDirectory(lr->coreDirectory, &SDL_Libretro_SetCoreDirectory_Iterator, (
void*)lr)) {
1860 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Failed to scan core directory '%s': %s", lr->coreDirectory, SDL_GetError());
1865 if (lr->coreLibraryCount > 1) {
1866 SDL_qsort(lr->coreLibrary, lr->coreLibraryCount,
sizeof(SDL_Libretro_CoreInfo), SDL_Libretro_CompareCoreInfo);
1882static bool SDL_Libretro_GetPrefDirectory(
char* dst,
size_t dstSize) {
1883 const char* appName = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
1884 const char* orgName = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING);
1885 char* prefPath = SDL_GetPrefPath(orgName && orgName[0] ? orgName :
"SDL_libretro", appName && appName[0] ? appName :
"SDL_libretro");
1886 if (!prefPath)
return false;
1889 size_t len = SDL_strlen(prefPath);
1890 while (len > 0 && (prefPath[len - 1] ==
'/' || prefPath[len - 1] ==
'\\')) {
1891 prefPath[--len] =
'\0';
1893 SDL_strlcpy(dst, prefPath, dstSize);
1895 return dst[0] !=
'\0';
1911 if (!lr)
return false;
1915 char pref[SDL_LIBRETRO_MAX_PATH];
1916 if (SDL_Libretro_GetPrefDirectory(pref,
sizeof(pref))) {
1917 SDL_snprintf(lr->saveDirectory,
sizeof(lr->saveDirectory),
"%s/saves", pref);
1920 lr->saveDirectory[0] =
'\0';
1925 SDL_strlcpy(lr->saveDirectory, path,
sizeof(lr->saveDirectory));
1941 if (!lr)
return false;
1945 char pref[SDL_LIBRETRO_MAX_PATH];
1946 if (SDL_Libretro_GetPrefDirectory(pref,
sizeof(pref))) {
1947 SDL_snprintf(lr->systemDirectory,
sizeof(lr->systemDirectory),
"%s/system", pref);
1950 lr->systemDirectory[0] =
'\0';
1955 SDL_strlcpy(lr->systemDirectory, path,
sizeof(lr->systemDirectory));
1969 if (!lr)
return false;
1970 SDL_strlcpy(lr->coreAssetsDirectory, path ? path :
"",
sizeof(lr->coreAssetsDirectory));
1983 if (!lr)
return NULL;
1984 return lr->coreDirectory[0] ? lr->coreDirectory : NULL;
1996 if (!lr)
return NULL;
1997 return lr->saveDirectory[0] ? lr->saveDirectory : NULL;
2009 if (!lr)
return NULL;
2010 return lr->systemDirectory[0] ? lr->systemDirectory : NULL;
2022 if (!lr)
return NULL;
2023 return lr->coreAssetsDirectory[0] ? lr->coreAssetsDirectory : NULL;
2038 if (!lr)
return false;
2039 SDL_strlcpy(lr->username, username ? username :
"",
sizeof(lr->username));
2052 if (!lr)
return NULL;
2053 return lr->username;
2068 lr->volume = SDL_clamp(volume, 0.0f, 1.0f);
2069 if (lr->core.audioStream) {
2070 SDL_SetAudioStreamGain(lr->core.audioStream, lr->volume);
2083 return lr ? lr->volume : 0.0f;
2108 if (lr->core.fastforwardOverrideActive && lr->core.fastforwardOverride.inhibit_toggle)
return;
2110 if (speed < 0.0f && lr->rewindEnabled && lr->rewindReference) {
2111 lr->core.speed = speed;
2112 if (lr->core.audioStream) {
2113 lr->core.drcAdjustment = 1.0f;
2114 lr->core.drcDriftAvg = 0.0;
2115 SDL_ClearAudioStream(lr->core.audioStream);
2118 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, -speed);
2122 lr->core.speed = SDL_max(speed, 0.0f);
2125 if (lr->core.audioStream && lr->core.speed > 0.0f) {
2126 lr->core.drcAdjustment = 1.0f;
2127 lr->core.drcDriftAvg = 0.0;
2128 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, lr->core.speed * lr->core.drcAdjustment);
2143 return lr ? lr->core.speed : 1.0f;
2158 return lr && lr->core.fastforwardOverrideActive;
2173 case SDL_LOG_PRIORITY_INFO: lr->logLevel = RETRO_LOG_INFO;
break;
2174 case SDL_LOG_PRIORITY_WARN: lr->logLevel = RETRO_LOG_WARN;
break;
2175 case SDL_LOG_PRIORITY_ERROR: lr->logLevel = RETRO_LOG_ERROR;
break;
2176 case SDL_LOG_PRIORITY_CRITICAL: lr->logLevel = RETRO_LOG_ERROR;
break;
2177 default: lr->logLevel = RETRO_LOG_DEBUG;
break;
2190 if (!lr)
return SDL_LOG_PRIORITY_INVALID;
2191 switch (lr->logLevel) {
2192 case RETRO_LOG_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
2193 case RETRO_LOG_INFO:
return SDL_LOG_PRIORITY_INFO;
2194 case RETRO_LOG_WARN:
return SDL_LOG_PRIORITY_WARN;
2195 case RETRO_LOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
2196 default:
return SDL_LOG_PRIORITY_INVALID;
2252 return lr ? lr->core.performanceLevel : 0;
2264 return lr ? lr->core.savestateContext : RETRO_SAVESTATE_CONTEXT_NORMAL;
2283 if (lr) lr->core.savestateContext = context;
2305 if (!lr || lr->core.contentPath[0] ==
'\0')
return "";
2306 return SDL_Libretro_GetExtension(lr->core.contentPath);
2316static bool SDL_Libretro_ExtensionInList(
const char* ext,
const char* pipeList) {
2317 if (!ext || !pipeList)
return false;
2318 size_t extLen = SDL_strlen(ext);
2319 const char* p = pipeList;
2321 const char* sep = SDL_strchr(p,
'|');
2322 size_t segLen = sep ? (size_t)(sep - p) : SDL_strlen(p);
2323 if (segLen == extLen && SDL_strncasecmp(p, ext, extLen) == 0)
return true;
#define SDL_LIBRETRO_MAX_USERS
The number of controller ports (gamepads) the frontend tracks.
#define LOAD_SYM(sym)
Load a required core symbol in lr->core.
#define SDL_LIBRETRO_GET_TICKS_NS()
The clock that paces SDL_Libretro_Update() and the frame-time callback, in nanoseconds.
#define SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES
Default ceiling on the encoded rewind history (delta data only), in bytes.
bool SDL_Libretro_IsCoreReady(const SDL_Libretro *lr)
Indicates whether a core is loaded.
bool SDL_Libretro_SetCoreAssetsDirectory(SDL_Libretro *lr, const char *path)
Sets where cores look for their assets, reported through RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY.
bool SDL_Libretro_SetSystemDirectory(SDL_Libretro *lr, const char *path)
Sets where cores look for system files (BIOS, firmware, etc).
size_t SDL_Libretro_GetSavePath(const SDL_Libretro *lr, const char *extension, char *dst, size_t dstSize)
Build a path in the save directory for the currently loaded content.
bool SDL_Libretro_SetCoreDirectory(SDL_Libretro *lr, const char *path)
Sets the associated libretro core directory, where the default set of cores will be loaded from.
const char * SDL_Libretro_GetUsername(SDL_Libretro *lr)
Get the username reported to cores.
const char * SDL_Libretro_GetContentExtension(const SDL_Libretro *lr)
Get the extension of the loaded content, as it appears in the path.
void SDL_Libretro_UnloadCore(SDL_Libretro *lr)
Unloads the actively loaded core.
bool SDL_Libretro_LoadGameSpecial(SDL_Libretro *lr, const char *subsystem, const char **paths, unsigned numPaths)
Loads multi-ROM subsystem content (e.g.
void SDL_Libretro_SetLogLevel(SDL_Libretro *lr, SDL_LogPriority level)
Sets the threshold for logs to be posted.
bool SDL_Libretro_Reset(SDL_Libretro *lr)
Resets the loaded game, like pressing the console's reset button.
size_t SDL_Libretro_GetFileName(char *dst, size_t dstSize, const char *path, bool withExtension)
Copy the file name portion of a path into a caller-provided buffer.
bool SDL_Libretro_PhysFS_LoadGame(SDL_Libretro *lr, const char *gamePath)
Archive-aware replacement for SDL_Libretro_LoadGame().
const char * SDL_Libretro_GetGameName(const SDL_Libretro *lr)
Retrieve the human-readable name of the loaded game.
int SDL_Libretro_GetVersion(void)
Get the version of SDL_libretro the implementation was compiled with.
const char * SDL_Libretro_GetCoreVersion(const SDL_Libretro *lr)
Retrieves the version of the libretro core that's actively loaded.
unsigned SDL_Libretro_GetPerformanceLevel(const SDL_Libretro *lr)
Get the performance level the core requested via SET_PERFORMANCE_LEVEL.
void SDL_Libretro_Update(SDL_Libretro *lr)
Runs the loaded game; call it once per frame of the application's loop.
const char * SDL_Libretro_GetValidExtensions(const SDL_Libretro *lr)
Gets the default set of valid extensions associated with the core, seperated by a "|".
bool SDL_Libretro_LoadGame_IO(SDL_Libretro *lr, SDL_IOStream *src, const char *path, bool closeio)
Loads a game from an SDL_IOStream.
bool SDL_Libretro_GetBlockExtract(const SDL_Libretro *lr)
Retrieves whether the core expects archives to be loaded directly.
void SDL_Libretro_SetVFS(SDL_Libretro *lr, void *vfs)
Set the Virtual File System the core uses for file access.
SDL_LogPriority SDL_Libretro_GetLogLevel(const SDL_Libretro *lr)
Retrieve the threshold for logs that will be posted.
#define SDL_LIBRETRO_VERSION
Retrieves an integer representation of the of the SDL_Libretro version.
float SDL_Libretro_GetSpeed(const SDL_Libretro *lr)
Get how fast the game runs, as a multiple of its normal speed.
void SDL_Libretro_PhysFS_Quit(SDL_Libretro *lr)
Tear down PhysFS, unmounting any active archive and restoring the plain SDL VFS on the given context.
#define SDL_EVENT_LIBRETRO
The base SDL event type for SDL_libretro events.
enum retro_savestate_context SDL_Libretro_GetSavestateContext(const SDL_Libretro *lr)
Get the savestate context reported to cores through RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT.
void SDL_Libretro_SetVolume(SDL_Libretro *lr, float volume)
Set the audio volume when playing sounds.
const char * SDL_Libretro_GetCoreAssetsDirectory(SDL_Libretro *lr)
Get the core assets directory.
#define SDL_EVENT_LIBRETRO_LAST
The last SDL event type in the SDL_libretro range.
struct SDL_Libretro SDL_Libretro
A libretro frontend context: one loaded core, its game, and the settings that carry across cores.
bool SDL_Libretro_LoadGame(SDL_Libretro *lr, const char *gamePath)
Loads a game at the given path.
bool SDL_Libretro_ShouldQuit(const SDL_Libretro *lr)
Indicates whether or not the core has requested to shutdown.
float SDL_Libretro_GetVolume(const SDL_Libretro *lr)
Get the audio volume.
void SDL_Libretro_Destroy(SDL_Libretro *lr)
Destroys the given libretro context.
#define SDL_EVENT_LIBRETRO_CORE_UNLOADED
The core was unloaded, along with any game it ran.
SDL_Libretro * SDL_Libretro_Create(void)
Creates a new libretro context.
#define SDL_EVENT_LIBRETRO_GAME_UNLOADED
The game was unloaded, whether directly or through the core unloading.
const char * SDL_Libretro_GetSystemDirectory(SDL_Libretro *lr)
Get the system directory.
void SDL_Libretro_UnloadGame(SDL_Libretro *lr)
Unloads the actively loaded game.
bool SDL_Libretro_SetSaveDirectory(SDL_Libretro *lr, const char *path)
Sets where content will be saved.
bool SDL_Libretro_LoadGameSpecialById(SDL_Libretro *lr, unsigned subsystemId, const char **paths, unsigned numPaths)
Load multi-ROM subsystem content by its numeric subsystem id.
bool SDL_Libretro_IsGameReady(const SDL_Libretro *lr)
Indicates whether a game is loaded and running.
#define SDL_EVENT_LIBRETRO_CORE_LOADED
A core finished loading; data2 is the core name, valid until the next load or unload.
bool SDL_Libretro_LoadCore(SDL_Libretro *lr, const char *core)
Loads a libretro core.
bool SDL_Libretro_SetUsername(SDL_Libretro *lr, const char *username)
Sets the username reported to cores through RETRO_ENVIRONMENT_GET_USERNAME.
bool SDL_Libretro_IsGameRequired(const SDL_Libretro *lr)
Indicates whether the loaded core needs content to run.
#define SDL_EVENT_LIBRETRO_ENVIRONMENT(cmd)
The SDL event type a RETRO_ENVIRONMENT_* command arrives as, when SDL_libretro leaves the command to ...
const char * SDL_Libretro_GetCoreDirectory(SDL_Libretro *lr)
Get the core directory.
const char * SDL_Libretro_GetSaveDirectory(SDL_Libretro *lr)
Get the save directory.
void SDL_Libretro_SetSavestateContext(SDL_Libretro *lr, enum retro_savestate_context context)
Sets the savestate context reported to cores through RETRO_ENVIRONMENT_GET_SAVESTATE_CONTEXT.
bool SDL_Libretro_IsFastforwardOverrideActive(const SDL_Libretro *lr)
Indicates whether the core is forcing fast-forward through RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERR...
void SDL_Libretro_SetLanguage(SDL_Libretro *lr, int language)
Override the language reported to cores through RETRO_ENVIRONMENT_GET_LANGUAGE.
#define SDL_EVENT_LIBRETRO_GAME_LOADED
A game finished loading, whether directly or through the menu; data2 is the game name,...
const char * SDL_Libretro_GetCoreName(const SDL_Libretro *lr)
Retrieve the name of the libretro core that's actively loaded.
void SDL_Libretro_SetSpeed(SDL_Libretro *lr, float speed)
Sets how fast the game runs, as a multiple of its normal speed.