7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_CORE_IMPL_ONCE)
8#define SDL_LIBRETRO_CORE_IMPL_ONCE
10#define LOAD_SYM(sym) do { \
11 SDL_FunctionPointer fp = SDL_LoadFunction(lr->core.symbols.handle, #sym); \
12 SDL_memcpy(&lr->core.symbols.sym, &fp, sizeof(fp)); \
14 SDL_SetError("[SDL_Libretro] Failed to load symbol '%s'", #sym); \
25 SDL_Libretro* lr = (SDL_Libretro*)SDL_calloc(1,
sizeof(SDL_Libretro));
27 SDL_SetError(
"[SDL_Libretro] Failed to allocate context");
33 SDL_Libretro_SetVFS(lr, NULL);
35 SDL_Libretro_SetSpeed(lr, 1.0f);
36 SDL_Libretro_SetUsername(lr,
"SDL_Libretro");
39 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_B] = SDL_SCANCODE_Z;
40 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_Y] = SDL_SCANCODE_A;
41 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_SELECT] = SDL_SCANCODE_RSHIFT;
42 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_START] = SDL_SCANCODE_RETURN;
43 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_UP] = SDL_SCANCODE_UP;
44 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_DOWN] = SDL_SCANCODE_DOWN;
45 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_LEFT] = SDL_SCANCODE_LEFT;
46 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_RIGHT] = SDL_SCANCODE_RIGHT;
47 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_A] = SDL_SCANCODE_X;
48 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_X] = SDL_SCANCODE_S;
49 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L] = SDL_SCANCODE_Q;
50 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R] = SDL_SCANCODE_W;
51 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L2] = SDL_SCANCODE_E;
52 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R2] = SDL_SCANCODE_R;
53 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_L3] = SDL_SCANCODE_D;
54 lr->keyboardPlayer1[RETRO_DEVICE_ID_JOYPAD_R3] = SDL_SCANCODE_F;
70 for (
unsigned i = 0; i < 16; i++) {
71 if (lr->gamepads[i]) {
72 SDL_CloseGamepad(lr->gamepads[i]);
73 lr->gamepads[i] = NULL;
78 SDL_Libretro_FreeCoreLibrary(lr);
79 SDL_Libretro_FreeMessages(lr);
80 SDL_Libretro_CloseConfig(lr);
92static const char* SDL_Libretro_GetCorePathFromName(
const SDL_Libretro* lr,
const char* coreName) {
93 if (!lr || !coreName || !lr->coreLibrary || SDL_strrchr(coreName,
'.') != NULL) {
96 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
97 if (lr->coreLibrary[i].corename && SDL_strcasecmp(lr->coreLibrary[i].corename, coreName) == 0) {
98 return lr->coreLibrary[i].path;
114 if (!lr || !corePath) {
115 SDL_SetError(
"[SDL_Libretro] Invalid arguments");
119 if (SDL_Libretro_active && SDL_Libretro_active != lr) {
120 SDL_SetError(
"[SDL_Libretro] Another context already has a core loaded");
128 const char* path = SDL_Libretro_GetCorePathFromName(lr, corePath);
134 lr->core.symbols.handle = SDL_LoadObject(corePath);
135 if (!lr->core.symbols.handle) {
136 SDL_SetError(
"[SDL_Libretro] Failed to load core '%s': %s", corePath, SDL_GetError());
141 LOAD_SYM(retro_api_version);
142 lr->core.apiVersion = lr->core.symbols.retro_api_version();
143 if (lr->core.apiVersion != 1) {
144 SDL_UnloadObject(lr->core.symbols.handle);
145 SDL_SetError(
"[SDL_Libretro] Unsupported Core API Version: %d", (
int)lr->core.apiVersion);
146 SDL_memset(&lr->core, 0,
sizeof(lr->core));
150 LOAD_SYM(retro_init);
151 LOAD_SYM(retro_deinit);
152 LOAD_SYM(retro_set_environment);
153 LOAD_SYM(retro_set_video_refresh);
154 LOAD_SYM(retro_set_audio_sample);
155 LOAD_SYM(retro_set_audio_sample_batch);
156 LOAD_SYM(retro_set_input_poll);
157 LOAD_SYM(retro_set_input_state);
158 LOAD_SYM(retro_get_system_info);
159 LOAD_SYM(retro_get_system_av_info);
160 LOAD_SYM(retro_set_controller_port_device);
161 LOAD_SYM(retro_reset);
163 LOAD_SYM(retro_serialize_size);
164 LOAD_SYM(retro_serialize);
165 LOAD_SYM(retro_unserialize);
166 LOAD_SYM(retro_cheat_reset);
167 LOAD_SYM(retro_cheat_set);
168 LOAD_SYM(retro_load_game);
169 LOAD_SYM(retro_load_game_special);
170 LOAD_SYM(retro_unload_game);
171 LOAD_SYM(retro_get_region);
172 LOAD_SYM(retro_get_memory_data);
173 LOAD_SYM(retro_get_memory_size);
175 SDL_strlcpy(lr->core.corePath, corePath,
sizeof(lr->core.corePath));
177 SDL_Libretro_active = lr;
180 lr->core.symbols.retro_set_environment(SDL_Libretro_EnvironmentCallback);
183 struct retro_system_info sysinfo = {0};
184 lr->core.symbols.retro_get_system_info(&sysinfo);
185 SDL_strlcpy(lr->core.libraryName, sysinfo.library_name ? sysinfo.library_name :
"",
sizeof(lr->core.libraryName));
186 SDL_strlcpy(lr->core.libraryVersion, sysinfo.library_version ? sysinfo.library_version :
"",
sizeof(lr->core.libraryVersion));
187 SDL_strlcpy(lr->core.validExtensions, sysinfo.valid_extensions ? sysinfo.valid_extensions :
"",
sizeof(lr->core.validExtensions));
188 lr->core.needFullpath = sysinfo.need_fullpath;
191 SDL_strlcpy(lr->core.contentName, lr->core.libraryName,
sizeof(lr->core.contentName));
194 SDL_Libretro_LoadCoreConfig(lr);
197 lr->core.symbols.retro_init();
198 lr->core.loaded =
true;
200 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Core loaded: %s %s", lr->core.libraryName, lr->core.libraryVersion);
213 if (!lr || !lr->core.loaded)
return;
216 SDL_Libretro_SaveCoreConfig(lr);
218 lr->core.symbols.retro_deinit();
219 if (lr->core.symbols.handle) {
220 SDL_UnloadObject(lr->core.symbols.handle);
223 SDL_Libretro_CloseSensors(lr);
224 SDL_Libretro_CloseMicrophone(lr);
225 SDL_Libretro_FreeCoreOptions(lr);
226 if (lr->core.inputDescriptors) {
227 SDL_free(lr->core.inputDescriptors);
229 if (lr->core.controllerInfo) {
230 SDL_free(lr->core.controllerInfo);
232 SDL_Libretro_FreeMemoryMap(lr);
233 SDL_Libretro_FreeContentInfoOverrides(lr);
235 SDL_memset(&lr->core, 0,
sizeof(lr->core));
236 if (SDL_Libretro_active == lr) {
237 SDL_Libretro_active = NULL;
240 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Core unloaded");
243bool SDL_Libretro_IsCoreReady(
const SDL_Libretro* lr) {
244 return lr && lr->core.loaded;
259 if (!dst || dstSize == 0)
return 0;
264 const char* base = SDL_strrchr(path,
'/');
265 if (!base) base = SDL_strrchr(path,
'\\');
266 base = base ? base + 1 : path;
268 SDL_strlcpy(dst, base, dstSize);
270 if (!withExtension) {
271 char* dot = SDL_strrchr(dst,
'.');
272 if (dot) *dot =
'\0';
275 return SDL_strlen(dst);
290 if (!dst || dstSize == 0)
return 0;
292 if (!lr || lr->core.contentName[0] ==
'\0')
return 0;
298 if (lr->saveDirectory[0] !=
'\0') {
299 SDL_snprintf(dst, dstSize,
"%s/%s%s", lr->saveDirectory, lr->core.contentName, extension);
301 SDL_snprintf(dst, dstSize,
"%s%s", lr->core.contentName, extension);
304 return SDL_strlen(dst);
312static void SDL_Libretro_FreeContentInfoOverrides(SDL_Libretro* lr) {
313 if (lr->core.contentInfoOverrides) {
314 for (
unsigned i = 0; i < lr->core.contentInfoOverrideCount; i++) {
315 SDL_free((
void*)lr->core.contentInfoOverrides[i].extensions);
317 SDL_free(lr->core.contentInfoOverrides);
318 lr->core.contentInfoOverrides = NULL;
320 lr->core.contentInfoOverrideCount = 0;
331static int SDL_Libretro_GetContentInfoOverride(
const SDL_Libretro* lr,
const char* ext) {
332 if (!lr || !ext || ext[0] ==
'\0')
return -1;
333 for (
unsigned i = 0; i < lr->core.contentInfoOverrideCount; i++) {
334 if (SDL_Libretro_ExtensionInList(ext, lr->core.contentInfoOverrides[i].extensions)) {
346static bool SDL_Libretro_ContentNeedsFullpath(
const SDL_Libretro* lr,
const char* ext) {
347 int i = SDL_Libretro_GetContentInfoOverride(lr, ext);
348 return i >= 0 ? lr->core.contentInfoOverrides[i].need_fullpath : lr->core.needFullpath;
356static bool SDL_Libretro_ContentPersistData(
const SDL_Libretro* lr,
const char* ext) {
357 int i = SDL_Libretro_GetContentInfoOverride(lr, ext);
358 return i >= 0 ? lr->core.contentInfoOverrides[i].persistent_data :
false;
368static void SDL_Libretro_ResetContentState(SDL_Libretro* lr) {
369 lr->core.contentPath[0] =
'\0';
370 lr->core.contentDir[0] =
'\0';
371 lr->core.contentExt[0] =
'\0';
372 SDL_strlcpy(lr->core.contentName, lr->core.libraryName,
sizeof(lr->core.contentName));
374 if (lr->core.gameInfoExt.persistent_data) {
375 SDL_free((
void*)lr->core.gameInfoExt.data);
377 SDL_memset(&lr->core.gameInfoExt, 0,
sizeof(lr->core.gameInfoExt));
385static bool SDL_Libretro_LoadCoreForGame(SDL_Libretro* lr,
const char* gamePath) {
386 if (!lr || !gamePath)
return false;
389 const char* dot = SDL_strrchr(gamePath,
'.');
390 const char* extension = dot ? dot + 1 :
"";
391 if (extension[0] ==
'\0') {
392 SDL_SetError(
"[SDL_Libretro] Game path '%s' has no file extension", gamePath);
397 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
399 if (!SDL_Libretro_ExtensionInList(extension, lr->coreLibrary[i].supported_extensions)) {
409 SDL_SetError(
"[SDL_Libretro] No core found for extension '%s'", extension);
425 if (!lr)
return false;
431 if (!SDL_Libretro_IsCoreReady(lr)) {
432 if (!SDL_Libretro_LoadCoreForGame(lr, gamePath)) {
433 SDL_SetError(
"[SDL_Libretro] Core not loaded");
439 if (!gamePath && !lr->core.supportNoGame) {
440 SDL_SetError(
"[SDL_Libretro] This core requires content");
444 struct retro_game_info gameInfo = {0};
445 void* fileData = NULL;
446 bool persistData =
false;
449 SDL_memset(&lr->core.gameInfoExt, 0,
sizeof(lr->core.gameInfoExt));
452 SDL_strlcpy(lr->core.contentPath, gamePath,
sizeof(lr->core.contentPath));
457 SDL_strlcpy(lr->core.contentExt, ext,
sizeof(lr->core.contentExt));
458 for (
char* c = lr->core.contentExt; *c; c++)
459 *c = (
char)SDL_tolower((
unsigned char)*c);
462 SDL_strlcpy(lr->core.contentDir, gamePath,
sizeof(lr->core.contentDir));
463 char* sep = SDL_strrchr(lr->core.contentDir,
'/');
464 if (!sep) sep = SDL_strrchr(lr->core.contentDir,
'\\');
468 lr->core.contentDir[0] =
'\0';
470 bool needFullpath = SDL_Libretro_ContentNeedsFullpath(lr, ext);
471 persistData = SDL_Libretro_ContentPersistData(lr, ext);
473 gameInfo.path = lr->core.contentPath;
477 fileData = SDL_LoadFile(gamePath, &fileSize);
479 SDL_SetError(
"[SDL_Libretro] Failed to load game file '%s'", gamePath);
480 SDL_Libretro_ResetContentState(lr);
483 gameInfo.data = fileData;
484 gameInfo.size = fileSize;
488 lr->core.gameInfoExt.full_path = lr->core.contentPath;
489 lr->core.gameInfoExt.dir = lr->core.contentDir;
490 lr->core.gameInfoExt.name = lr->core.contentName;
491 lr->core.gameInfoExt.ext = lr->core.contentExt;
492 lr->core.gameInfoExt.data = gameInfo.data;
493 lr->core.gameInfoExt.size = gameInfo.size;
494 lr->core.gameInfoExt.persistent_data = persistData;
498 lr->core.symbols.retro_set_video_refresh(SDL_Libretro_VideoRefresh);
499 lr->core.symbols.retro_set_audio_sample(SDL_Libretro_AudioSample);
500 lr->core.symbols.retro_set_audio_sample_batch(SDL_Libretro_AudioSampleBatch);
501 lr->core.symbols.retro_set_input_poll(SDL_Libretro_InputPoll);
502 lr->core.symbols.retro_set_input_state(SDL_Libretro_InputState);
504 bool result = lr->core.symbols.retro_load_game(gamePath ? &gameInfo : NULL);
507 if (fileData && !(persistData && result)) {
509 lr->core.gameInfoExt.data = NULL;
510 lr->core.gameInfoExt.size = 0;
514 SDL_Libretro_ResetContentState(lr);
515 SDL_SetError(
"[SDL_Libretro] Core failed to load the game");
519 lr->core.gameLoaded =
true;
522 struct retro_system_av_info avInfo = {0};
523 lr->core.symbols.retro_get_system_av_info(&avInfo);
524 lr->core.width = avInfo.geometry.base_width;
525 lr->core.height = avInfo.geometry.base_height;
526 lr->core.fps = avInfo.timing.fps;
527 lr->core.sampleRate = avInfo.timing.sample_rate;
528 lr->core.aspectRatio = avInfo.geometry.aspect_ratio;
533 if (!SDL_Libretro_InitVideo(lr)) {
534 SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Video failed to initialize: %s", SDL_GetError());
539 if (!SDL_Libretro_InitAudio(lr)) {
540 SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO,
"[SDL_Libretro] Audio failed to initialize: %s", SDL_GetError());
543 SDL_Log(
"[SDL_Libretro] Game loaded: %s [%ux%u @ %.2ffps]", lr->core.contentName,
544 lr->core.width, lr->core.height, lr->core.fps);
547 if (lr->rewindEnabled && !lr->rewindReference && lr->rewindCapacity > 0) {
560 if (!lr || !lr->core.gameLoaded)
return;
563 lr->core.symbols.retro_unload_game();
564 lr->core.gameLoaded =
false;
565 SDL_Libretro_ResetContentState(lr);
567 SDL_Libretro_RewindFree(lr);
568 SDL_Libretro_CloseAudio(lr);
569 SDL_Libretro_CloseVideo(lr);
570 SDL_Log(
"[SDL_Libretro] Game unloaded");
573bool SDL_Libretro_IsGameReady(
const SDL_Libretro* lr) {
574 return lr && lr->core.gameLoaded;
577bool SDL_Libretro_IsGameRequired(
const SDL_Libretro* lr) {
578 return lr && !lr->core.supportNoGame;
581bool SDL_Libretro_Reset(SDL_Libretro* lr) {
582 if (!lr || !lr->core.gameLoaded) {
583 SDL_SetError(
"[SDL_Libretro] No game loaded");
586 lr->core.symbols.retro_reset();
589 SDL_Libretro_ClearRewind(lr);
592 if (lr->core.audioStream) {
593 SDL_ClearAudioStream(lr->core.audioStream);
594 lr->core.singleSampleCount = 0;
595 lr->core.drcDriftAvg = 0.0;
603static void SDL_Libretro_Tick(SDL_Libretro* lr, retro_usec_t referenceUsec) {
604 if (lr->core.runloop_frame_time.callback) {
605 retro_usec_t delta = referenceUsec;
607 if (!lr->core.runloop_frame_time_last) {
608 delta = lr->core.runloop_frame_time.reference;
610 lr->core.runloop_frame_time_last = referenceUsec;
611 lr->core.runloop_frame_time.callback(delta);
615 SDL_Libretro_ReportAudioBufferStatus(lr);
618 lr->core.symbols.retro_run();
621 SDL_Libretro_RewindCapture(lr);
624 if (lr->core.audioStream && lr->core.audio_callback.callback) {
625 lr->core.audio_callback.callback();
629void SDL_Libretro_Update(SDL_Libretro* lr) {
630 if (!lr || !lr->core.gameLoaded)
return;
633 if (lr->core.videoReinitPending) {
634 lr->core.videoReinitPending =
false;
635 SDL_Libretro_InitVideo(lr);
639 if (lr->core.audioReinitPending) {
640 lr->core.audioReinitPending =
false;
641 SDL_Libretro_InitAudio(lr);
645 if (lr->speed == 0.0f)
return;
648 if (lr->rewindEnabled && lr->speed < 0.0f) {
649 Uint64 nowNS = SDL_GetTicksNS();
650 if (lr->lastTickNS == 0) {
651 lr->lastTickNS = nowNS;
653 double frameTime = (double)(nowNS - lr->lastTickNS) / 1.0e9;
654 lr->lastTickNS = nowNS;
655 double framePeriod = (lr->core.fps > 0.0) ? (1.0 / lr->core.fps) : (1.0 / 60.0);
657 unsigned interval = lr->rewindCaptureInterval > 0 ? lr->rewindCaptureInterval : 1;
658 double stepPeriod = framePeriod * (double)interval;
659 lr->speedAccumulator += frameTime * (double)(-lr->speed);
661 lr->rewindActive =
true;
662 while (lr->speedAccumulator >= stepPeriod) {
663 lr->speedAccumulator -= stepPeriod;
664 if (!SDL_Libretro_RewindStepState(lr))
break;
665 lr->core.symbols.retro_run();
667 lr->rewindActive =
false;
672 SDL_Libretro_UpdateDRC(lr, lr->speed);
675 Uint64 nowNS = SDL_GetTicksNS();
676 if (lr->lastTickNS == 0) {
678 lr->lastTickNS = nowNS;
679 lr->speedAccumulator = 0.0;
680 SDL_Libretro_Tick(lr, 0);
685 double frameTime = (double)(nowNS - lr->lastTickNS) / 1.0e9;
686 lr->lastTickNS = nowNS;
689 double framePeriod = (lr->core.fps > 0.0) ? (1.0 / lr->core.fps) : (1.0 / 60.0);
692 retro_usec_t referenceUsec = (retro_usec_t)(framePeriod * 1.0e6 / (
double)lr->speed);
695 double cadence = (framePeriod > 0.0) ? (frameTime / framePeriod) : 0.0;
696 if (lr->speed == 1.0f && cadence > 0.9 && cadence < 1.1) {
697 lr->speedAccumulator = 0.0;
698 SDL_Libretro_Tick(lr, referenceUsec);
702 lr->speedAccumulator += frameTime * (double)lr->speed;
705 int maxTicks = (int)(lr->speed + 1.0f);
706 if (maxTicks < 1) maxTicks = 1;
709 double maxAccumulator = framePeriod * (double)maxTicks;
710 if (lr->speedAccumulator > maxAccumulator) {
711 lr->speedAccumulator = maxAccumulator;
715 while (lr->speedAccumulator >= framePeriod && maxTicks-- > 0) {
716 lr->speedAccumulator -= framePeriod;
717 SDL_Libretro_Tick(lr, referenceUsec);
727 return lr && lr->core.shutdown;
730int SDL_Libretro_Version(
void) {
736static void SDL_Libretro_FreeCoreLibrary(SDL_Libretro* lr) {
737 if (lr->coreLibrary) {
738 for (
unsigned i = 0; i < lr->coreLibraryCount; i++) {
739 SDL_free(lr->coreLibrary[i].corename);
740 SDL_free(lr->coreLibrary[i].supported_extensions);
741 SDL_free(lr->coreLibrary[i].path);
743 SDL_free(lr->coreLibrary);
744 lr->coreLibrary = NULL;
746 lr->coreLibraryCount = 0;
750#if defined(SDL_PLATFORM_WINDOWS)
751#define SDL_LIBRETRO_CORE_EXTENSION ".dll"
752#elif defined(SDL_PLATFORM_APPLE)
753#define SDL_LIBRETRO_CORE_EXTENSION ".dylib"
754#elif defined(SDL_PLATFORM_EMSCRIPTEN)
755#define SDL_LIBRETRO_CORE_EXTENSION ".wasm"
757#define SDL_LIBRETRO_CORE_EXTENSION ".so"
760static SDL_EnumerationResult SDLCALL SDL_Libretro_SetCoreDirectory_Iterator(
void *userdata,
const char *dirname,
const char *fname) {
761 SDL_Libretro* lr = (SDL_Libretro*)userdata;
764 const char* dot = SDL_strrchr(fname,
'.');
765 if (!dot || SDL_strcasecmp(dot,
".info") != 0) {
766 return SDL_ENUM_CONTINUE;
770 char infoPath[SDL_LIBRETRO_MAX_PATH];
771 SDL_snprintf(infoPath,
sizeof(infoPath),
"%s%s", dirname, fname);
774 SDL_ini* ini = INI_Load(infoPath);
776 return SDL_ENUM_CONTINUE;
780 const char* corename = INI_GetString(ini, NULL,
"corename", NULL);
783 return SDL_ENUM_CONTINUE;
787 SDL_Libretro_CoreInfo* grown = (SDL_Libretro_CoreInfo*)SDL_realloc(
788 lr->coreLibrary, (lr->coreLibraryCount + 1) *
sizeof(SDL_Libretro_CoreInfo));
791 return SDL_ENUM_CONTINUE;
793 lr->coreLibrary = grown;
796 SDL_Libretro_CoreInfo* entry = &lr->coreLibrary[lr->coreLibraryCount];
797 entry->corename = SDL_strdup(corename);
799 const char* extensions = INI_GetString(ini, NULL,
"supported_extensions", NULL);
800 entry->supported_extensions = extensions ? SDL_strdup(extensions) : NULL;
801 entry->needs_fullpath = INI_GetBoolean(ini, NULL,
"needs_fullpath",
false);
802 entry->supports_no_game = INI_GetBoolean(ini, NULL,
"supports_no_game",
false);
805 char base[SDL_LIBRETRO_MAX_PATH];
806 size_t baseLen = (size_t)(dot - fname);
807 SDL_strlcpy(base, fname, (baseLen <
sizeof(base)) ? baseLen + 1 : sizeof(base));
808 char corePath[SDL_LIBRETRO_MAX_PATH];
809 SDL_snprintf(corePath,
sizeof(corePath),
"%s%s%s", dirname, base, SDL_LIBRETRO_CORE_EXTENSION);
810 entry->path = SDL_strdup(corePath);
812 lr->coreLibraryCount++;
815 return SDL_ENUM_CONTINUE;
822 if (!lr)
return false;
823 const char* newPath = path ? path :
"";
824 SDL_strlcpy(lr->coreDirectory, newPath,
sizeof(lr->coreDirectory));
827 SDL_Libretro_FreeCoreLibrary(lr);
828 SDL_EnumerateDirectory(lr->coreDirectory, &SDL_Libretro_SetCoreDirectory_Iterator, (
void*)lr);
832bool SDL_Libretro_SetSaveDirectory(SDL_Libretro* lr,
const char* path) {
833 if (!lr)
return false;
834 SDL_strlcpy(lr->saveDirectory, path ? path :
"", sizeof(lr->saveDirectory));
838bool SDL_Libretro_SetSystemDirectory(SDL_Libretro* lr,
const char* path) {
839 if (!lr)
return false;
840 SDL_strlcpy(lr->systemDirectory, path ? path :
"", sizeof(lr->systemDirectory));
844bool SDL_Libretro_SetCoreAssetsDirectory(SDL_Libretro* lr,
const char* path) {
845 if (!lr)
return false;
846 SDL_strlcpy(lr->coreAssetsDirectory, path ? path :
"", sizeof(lr->coreAssetsDirectory));
850const char* SDL_Libretro_GetCoreDirectory(SDL_Libretro* lr) {
851 if (!lr)
return NULL;
852 return lr->coreDirectory[0] ? lr->coreDirectory : NULL;
855const char* SDL_Libretro_GetSaveDirectory(SDL_Libretro* lr) {
856 if (!lr)
return NULL;
857 return lr->saveDirectory[0] ? lr->saveDirectory : NULL;
860const char* SDL_Libretro_GetSystemDirectory(SDL_Libretro* lr) {
861 if (!lr)
return NULL;
862 return lr->systemDirectory[0] ? lr->systemDirectory : NULL;
865const char* SDL_Libretro_GetCoreAssetsDirectory(SDL_Libretro* lr) {
866 if (!lr)
return NULL;
867 return lr->coreAssetsDirectory[0] ? lr->coreAssetsDirectory : NULL;
870bool SDL_Libretro_SetUsername(SDL_Libretro* lr,
const char* username) {
871 if (!lr)
return false;
872 SDL_strlcpy(lr->username, username ? username :
"", sizeof(lr->username));
876const char* SDL_Libretro_GetUsername(SDL_Libretro* lr) {
877 if (!lr)
return NULL;
886 lr->volume = SDL_clamp(volume, 0.0f, 1.0f);
887 if (lr->core.audioStream) {
888 SDL_SetAudioStreamGain(lr->core.audioStream, lr->volume);
892float SDL_Libretro_GetVolume(
const SDL_Libretro* lr) {
893 return lr ? lr->volume : 0.0f;
896void SDL_Libretro_SetSpeed(SDL_Libretro* lr,
float speed) {
899 if (speed < 0.0f && lr->rewindEnabled) {
901 if (lr->core.audioStream) {
902 lr->core.drcAdjustment = 1.0f;
903 lr->core.drcDriftAvg = 0.0;
904 SDL_ClearAudioStream(lr->core.audioStream);
907 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, -speed);
910 lr->speed = SDL_max(speed, 0.0f);
913 if (lr->core.audioStream && lr->speed > 0.0f) {
914 lr->core.drcAdjustment = 1.0f;
915 lr->core.drcDriftAvg = 0.0;
916 SDL_SetAudioStreamFrequencyRatio(lr->core.audioStream, lr->speed * lr->core.drcAdjustment);
920float SDL_Libretro_GetSpeed(
const SDL_Libretro* lr) {
921 return lr ? lr->speed : 1.0f;
932 case SDL_LOG_PRIORITY_INFO: lr->logLevel = RETRO_LOG_INFO;
break;
933 case SDL_LOG_PRIORITY_WARN: lr->logLevel = RETRO_LOG_WARN;
break;
934 case SDL_LOG_PRIORITY_ERROR: lr->logLevel = RETRO_LOG_ERROR;
break;
935 case SDL_LOG_PRIORITY_CRITICAL: lr->logLevel = RETRO_LOG_ERROR;
break;
936 default: lr->logLevel = RETRO_LOG_DEBUG;
break;
944 if (!lr)
return SDL_LOG_PRIORITY_INVALID;
945 switch (lr->logLevel) {
946 case RETRO_LOG_DEBUG:
return SDL_LOG_PRIORITY_DEBUG;
947 case RETRO_LOG_INFO:
return SDL_LOG_PRIORITY_INFO;
948 case RETRO_LOG_WARN:
return SDL_LOG_PRIORITY_WARN;
949 case RETRO_LOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
950 default:
return SDL_LOG_PRIORITY_INVALID;
958 return (lr && lr->core.loaded) ? lr->core.libraryName :
"";
965 return (lr && lr->core.loaded) ? lr->core.libraryVersion :
"";
972 return (lr && lr->core.loaded) ? lr->core.validExtensions :
"";
984 return lr ? lr->core.performanceLevel : 0;
996 if (!lr || lr->core.contentPath[0] ==
'\0')
return "";
997 const char* dot = SDL_strrchr(lr->core.contentPath,
'.');
998 return dot ? dot + 1 :
"";
1001static bool SDL_Libretro_ExtensionInList(
const char* ext,
const char* pipeList) {
1002 if (!ext || !pipeList)
return false;
1003 size_t extLen = SDL_strlen(ext);
1004 const char* p = pipeList;
1006 const char* sep = SDL_strchr(p,
'|');
1007 size_t segLen = sep ? (size_t)(sep - p) : SDL_strlen(p);
1008 if (segLen == extLen && SDL_strncasecmp(p, ext, extLen) == 0)
return true;
#define SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES
Default ceiling on the encoded rewind history (delta data only), in bytes.
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_GetContentExtension(const SDL_Libretro *lr)
Get the extension of the loaded content, as it appears in the path.
bool SDL_Libretro_LoadCore(SDL_Libretro *lr, const char *corePath)
Loads a libretro core.
void SDL_Libretro_UnloadCore(SDL_Libretro *lr)
Unloads the actively loaded core.
void SDL_Libretro_SetLogLevel(SDL_Libretro *lr, SDL_LogPriority level)
Sets the threshold for logs to be posted.
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_SetRewindEnabled(SDL_Libretro *lr, bool enabled, unsigned bufferFrames, unsigned captureInterval)
Enable or disable the rewind system.
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.
const char * SDL_Libretro_GetValidExtensions(const SDL_Libretro *lr)
Gets the default set of valid extensions associated with the core, seperated by a "|".
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.
void SDL_Libretro_SetVolume(SDL_Libretro *lr, float volume)
Set the audio volume when playing sounds.
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.
void SDL_Libretro_Destroy(SDL_Libretro *lr)
Destroys the given libretro context.
SDL_Libretro * SDL_Libretro_Create(void)
Builds a libretro context.
void SDL_Libretro_UnloadGame(SDL_Libretro *lr)
Unloads the actively loaded game.
const char * SDL_Libretro_GetCoreName(const SDL_Libretro *lr)
Retrieve the name of the libretro core that's actively loaded.