SDL_Libretro 0.1.0
SDL3-power libretro frontend.
Loading...
Searching...
No Matches
SDL_libretro_serialize.h
Go to the documentation of this file.
1
7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_SERIALIZE_IMPL_ONCE)
8#define SDL_LIBRETRO_SERIALIZE_IMPL_ONCE
9
10#ifdef __DOXYGEN
21#define SDL_LIBRETRO_ENABLE_REWIND_DELTA
22#endif
23
24
25#ifndef SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES
31#define SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES ((size_t)256 * 1024 * 1024)
32#endif
33
34// Save Files
35
49static SDL_IOStream* SDL_Libretro_OpenSaveFile(const char* file, char* tempPath, size_t tempPathSize) {
50 if ((size_t)SDL_snprintf(tempPath, tempPathSize, "%s.tmp", file) >= tempPathSize) {
51 SDL_SetError("[SDL_Libretro] Save path is too long: %s", file);
52 return NULL;
53 }
54 return SDL_IOFromFile(tempPath, "wb");
55}
56
69static bool SDL_Libretro_CommitSaveFile(SDL_IOStream* io, bool written, const char* tempPath, const char* file) {
70 // Writers like SDL_IOprintf() don't all report failures, but the stream
71 // remembers them; and closing flushes, so a full disk can fail there too.
72 bool ok = written && SDL_GetIOStatus(io) != SDL_IO_STATUS_ERROR;
73 ok = SDL_CloseIO(io) && ok;
74 if (ok && !SDL_RenamePath(tempPath, file)) {
75 ok = false;
76 }
77 if (!ok) {
78 SDL_RemovePath(tempPath);
79 }
80 return ok;
81}
82
83// Disk Control
84
91unsigned SDL_Libretro_GetDiskCount(const SDL_Libretro* lr) {
92 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.disk_control.get_num_images) return 0;
93 return lr->core.disk_control.get_num_images();
94}
95
102unsigned SDL_Libretro_GetDiskIndex(const SDL_Libretro* lr) {
103 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.disk_control.get_image_index) return 0;
104 return lr->core.disk_control.get_image_index();
105}
106
116bool SDL_Libretro_SetDiskIndex(SDL_Libretro* lr, unsigned index) {
117 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.disk_control.set_image_index) {
118 SDL_SetError("[SDL_Libretro] Disk control not available");
119 return false;
120 }
121 if (!lr->core.disk_control.get_eject_state || !lr->core.disk_control.get_eject_state()) {
122 SDL_SetError("[SDL_Libretro] Disk tray must be ejected before changing index");
123 return false;
124 }
125 return lr->core.disk_control.set_image_index(index);
126}
127
134bool SDL_Libretro_EjectDisk(SDL_Libretro* lr) {
135 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.disk_control.set_eject_state) {
136 SDL_SetError("[SDL_Libretro] Disk control not available");
137 return false;
138 }
139 return lr->core.disk_control.set_eject_state(true);
140}
141
148bool SDL_Libretro_InsertDisk(SDL_Libretro* lr) {
149 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.disk_control.set_eject_state) {
150 SDL_SetError("[SDL_Libretro] Disk control not available");
151 return false;
152 }
153 return lr->core.disk_control.set_eject_state(false);
154}
155
164static void SDL_Libretro_FreeDiskImages(SDL_Libretro* lr) {
165 if (!lr) return;
166 for (unsigned i = 0; i < lr->core.diskImageCount; i++) {
167 SDL_LibretroDiskImage* image = &lr->core.diskImages[i];
168 if (image->temp && image->path) {
169 SDL_RemovePath(image->path);
170 }
171 SDL_free(image->path);
172 SDL_free(image->data);
173 }
174 SDL_free(lr->core.diskImages);
175 lr->core.diskImages = NULL;
176 lr->core.diskImageCount = 0;
177}
178
183static bool SDL_Libretro_CanAddDiskImage(SDL_Libretro* lr) {
184 if (!lr->core.disk_control.add_image_index || !lr->core.disk_control.replace_image_index || !lr->core.disk_control.get_num_images) {
185 SDL_SetError("[SDL_Libretro] Disk control not available");
186 return false;
187 }
188 if (!lr->core.disk_control.get_eject_state || !lr->core.disk_control.get_eject_state()) {
189 SDL_SetError("[SDL_Libretro] Disk tray must be ejected before adding a disk image");
190 return false;
191 }
192 return true;
193}
194
202static bool SDL_Libretro_AppendDiskImage(SDL_Libretro* lr, const struct retro_game_info* info) {
203 unsigned before = lr->core.disk_control.get_num_images();
204 if (!lr->core.disk_control.add_image_index()) {
205 SDL_SetError("[SDL_Libretro] Core rejected add_image_index");
206 return false;
207 }
208 unsigned count = lr->core.disk_control.get_num_images();
209 if (count == 0 || count <= before) {
210 SDL_SetError("[SDL_Libretro] Core reported no new disk image after add");
211 return false;
212 }
213 if (!lr->core.disk_control.replace_image_index(count - 1, info)) {
214 lr->core.disk_control.replace_image_index(count - 1, NULL);
215 SDL_SetError("[SDL_Libretro] Core rejected the disk image");
216 return false;
217 }
218 return true;
219}
220
237bool SDL_Libretro_AddDiskImage(SDL_Libretro* lr, const char* path) {
238 if (!SDL_Libretro_IsGameReady(lr) || !path) {
239 SDL_SetError("[SDL_Libretro] Invalid AddDiskImage arguments");
240 return false;
241 }
242 if (!SDL_Libretro_CanAddDiskImage(lr)) {
243 return false;
244 }
245 if (!SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(path))) {
246 // The core wants the bytes; the stream loader reads and keeps them.
247 SDL_IOStream* src = SDL_IOFromFile(path, "rb");
248 if (!src) {
249 SDL_SetError("[SDL_Libretro] Failed to open disk image '%s'", path);
250 return false;
251 }
252 return SDL_Libretro_AddDiskImage_IO(lr, src, path, true);
253 }
254 struct retro_game_info info;
255 SDL_memset(&info, 0, sizeof(info));
256 info.path = path;
257 return SDL_Libretro_AppendDiskImage(lr, &info);
258}
259
280bool SDL_Libretro_AddDiskImage_IO(SDL_Libretro* lr, SDL_IOStream* src, const char* path, bool closeio) {
281 if (!SDL_Libretro_IsGameReady(lr) || !src || !path) {
282 SDL_SetError("[SDL_Libretro] Invalid AddDiskImage_IO arguments");
283 if (closeio && src) SDL_CloseIO(src);
284 return false;
285 }
286 if (!SDL_Libretro_CanAddDiskImage(lr)) {
287 if (closeio) SDL_CloseIO(src);
288 return false;
289 }
290
291 // Make room to keep the image alive until the game unloads.
292 SDL_LibretroDiskImage* images = (SDL_LibretroDiskImage*)SDL_realloc(lr->core.diskImages, (lr->core.diskImageCount + 1) * sizeof(SDL_LibretroDiskImage));
293 if (!images) {
294 if (closeio) SDL_CloseIO(src);
295 return false;
296 }
297 lr->core.diskImages = images;
298 SDL_LibretroDiskImage image = {0};
299 struct retro_game_info info;
300 SDL_memset(&info, 0, sizeof(info));
301
302 // Read or spill the image before mutating the core's disk list, so a
303 // failed read does not leave an empty slot behind.
304 if (SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(path))) {
305 char tempPath[SDL_LIBRETRO_MAX_PATH];
306 if (!SDL_Libretro_SpillToTempFile(lr, src, path, closeio, tempPath, sizeof(tempPath))) {
307 return false;
308 }
309 image.temp = true;
310 image.path = SDL_strdup(tempPath);
311 if (!image.path) {
312 SDL_RemovePath(tempPath);
313 return false;
314 }
315 }
316 else {
317 image.data = SDL_LoadFile_IO(src, &info.size, closeio);
318 if (!image.data) return false;
319 image.path = SDL_strdup(path);
320 if (!image.path) {
321 SDL_free(image.data);
322 return false;
323 }
324 }
325 info.path = image.path;
326 info.data = image.data;
327
328 if (!SDL_Libretro_AppendDiskImage(lr, &info)) {
329 if (image.temp) SDL_RemovePath(image.path);
330 SDL_free(image.path);
331 SDL_free(image.data);
332 return false;
333 }
334 lr->core.diskImages[lr->core.diskImageCount++] = image;
335 return true;
336}
337
349bool SDL_Libretro_GetDiskLabel(const SDL_Libretro* lr, unsigned index, char* label, size_t len) {
350 if (!SDL_Libretro_IsGameReady(lr) || !label || len == 0) {
351 SDL_SetError("[SDL_Libretro] Invalid GetDiskLabel arguments");
352 return false;
353 }
354 if (!lr->core.disk_control.get_image_label) {
355 SDL_SetError("[SDL_Libretro] Disk labels not available");
356 return false;
357 }
358 label[0] = '\0';
359 return lr->core.disk_control.get_image_label(index, label, len);
360}
361
373bool SDL_Libretro_SetInitialDisk(SDL_Libretro* lr, unsigned index, const char* path) {
374 if (!lr || !path) {
375 SDL_SetError("[SDL_Libretro] Invalid SetInitialDisk arguments");
376 return false;
377 }
378 if (!lr->core.disk_control.set_initial_image) {
379 SDL_SetError("[SDL_Libretro] Disk control not available");
380 return false;
381 }
382 return lr->core.disk_control.set_initial_image(index, path);
383}
384
385// Cheats
386
400bool SDL_Libretro_SetCheat(SDL_Libretro* lr, unsigned index, bool enabled, const char* code) {
401 if (!SDL_Libretro_IsGameReady(lr) || !code) {
402 SDL_SetError("[SDL_Libretro] Invalid SetCheat arguments");
403 return false;
404 }
405 lr->core.symbols.retro_cheat_set(index, enabled, code);
406 return true;
407}
408
418void SDL_Libretro_ResetCheats(SDL_Libretro* lr) {
419 if (!SDL_Libretro_IsGameReady(lr)) return;
420 lr->core.symbols.retro_cheat_reset();
421}
422
423// Save States
424
431size_t SDL_Libretro_GetStateSize(const SDL_Libretro* lr) {
432 if (!SDL_Libretro_IsGameReady(lr)) return 0;
433 return lr->core.symbols.retro_serialize_size();
434}
435
447bool SDL_Libretro_SaveState_IO(SDL_Libretro* lr, SDL_IOStream* dst, bool closeio) {
448 bool ok = false;
449 if (!SDL_Libretro_IsGameReady(lr) || !dst) {
450 SDL_SetError("[SDL_Libretro] Invalid SaveState_IO arguments");
451 }
452 else {
453 size_t size = lr->core.symbols.retro_serialize_size();
454 if (size == 0) {
455 SDL_SetError("[SDL_Libretro] Core does not support save states");
456 }
457 else {
458 void* data = SDL_malloc(size);
459 if (!data) {
460 SDL_SetError("[SDL_Libretro] Failed to allocate save state buffer");
461 }
462 else {
463 if (lr->core.symbols.retro_serialize(data, size)) {
464 // On a short write, SDL_WriteIO sets its own error.
465 ok = (SDL_WriteIO(dst, data, size) == size);
466 }
467 else {
468 SDL_SetError("[SDL_Libretro] Core failed to serialize state");
469 }
470 SDL_free(data);
471 }
472 }
473 }
474 if (closeio && dst) SDL_CloseIO(dst);
475 return ok;
476}
477
488bool SDL_Libretro_SaveState(SDL_Libretro* lr, const char* file) {
489 if (!SDL_Libretro_IsGameReady(lr) || !file) {
490 SDL_SetError("[SDL_Libretro] Invalid SaveState arguments");
491 return false;
492 }
493
494 char tempPath[SDL_LIBRETRO_MAX_PATH];
495 SDL_IOStream* io = SDL_Libretro_OpenSaveFile(file, tempPath, sizeof(tempPath));
496 if (!io) return false;
497 return SDL_Libretro_CommitSaveFile(io, SDL_Libretro_SaveState_IO(lr, io, false), tempPath, file);
498}
499
514bool SDL_Libretro_LoadState_IO(SDL_Libretro* lr, SDL_IOStream* src, bool closeio) {
515 if (!SDL_Libretro_IsGameReady(lr) || !src) {
516 SDL_SetError("[SDL_Libretro] Invalid LoadState_IO arguments");
517 if (closeio && src) SDL_CloseIO(src);
518 return false;
519 }
520 size_t size = 0;
521 void* data = SDL_LoadFile_IO(src, &size, closeio);
522 if (!data) return false;
523 bool ok = false;
524 if (size == 0) {
525 SDL_SetError("[SDL_Libretro] Save state is empty");
526 }
527 else if (!lr->core.symbols.retro_unserialize(data, size)) {
528 SDL_SetError("[SDL_Libretro] Core rejected save state");
529 }
530 else {
531 ok = true;
532 }
533 SDL_free(data);
534 // A load is a timeline discontinuity: drop rewind history so a subsequent rewind can't walk back across it into the pre-load timeline.
535 if (ok && lr->rewindEnabled) SDL_Libretro_ClearRewind(lr);
536 return ok;
537}
538
549bool SDL_Libretro_LoadState(SDL_Libretro* lr, const char* file) {
550 if (!SDL_Libretro_IsGameReady(lr) || !file) {
551 SDL_SetError("[SDL_Libretro] Invalid LoadState arguments");
552 return false;
553 }
554 SDL_IOStream* io = SDL_IOFromFile(file, "rb");
555 if (!io) return false;
556 return SDL_Libretro_LoadState_IO(lr, io, true);
557}
558
559// Memory
560
573void* SDL_Libretro_GetMemoryData(const SDL_Libretro* lr, unsigned memoryType, size_t* size) {
574 if (!SDL_Libretro_IsGameReady(lr) || !lr->core.symbols.retro_get_memory_data || !lr->core.symbols.retro_get_memory_size) {
575 if (size) *size = 0;
576 return NULL;
577 }
578 void* ptr = lr->core.symbols.retro_get_memory_data(memoryType);
579 if (size) *size = lr->core.symbols.retro_get_memory_size(memoryType);
580 return ptr;
581}
582
596bool SDL_Libretro_SetMemoryData(SDL_Libretro* lr, unsigned memoryType, const void* data, size_t size) {
597 if (!lr || !data) {
598 SDL_SetError("[SDL_Libretro] Invalid SetMemoryData arguments");
599 return false;
600 }
601 size_t capacity = 0;
602 void* dst = SDL_Libretro_GetMemoryData(lr, memoryType, &capacity);
603 if (!dst || capacity == 0) {
604 SDL_SetError("[SDL_Libretro] Memory type %u unavailable", memoryType);
605 return false;
606 }
607 size_t copySize = size < capacity ? size : capacity;
608 SDL_memcpy(dst, data, copySize);
609 return true;
610}
611
617static void SDL_Libretro_FreeMemoryMap(SDL_Libretro* lr) {
618 if (!lr) return;
619 if (lr->core.memoryMapDescriptors) {
620 for (unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
621 SDL_free((void*)lr->core.memoryMapDescriptors[i].addrspace);
622 }
623 SDL_free(lr->core.memoryMapDescriptors);
624 lr->core.memoryMapDescriptors = NULL;
625 }
626 lr->core.memoryMapDescriptorCount = 0;
627}
628
641unsigned SDL_Libretro_GetMemoryMapCount(const SDL_Libretro* lr) {
642 return SDL_Libretro_IsGameReady(lr) ? lr->core.memoryMapDescriptorCount : 0;
643}
644
666bool SDL_Libretro_GetMemoryMapDescriptor(const SDL_Libretro* lr, unsigned index, Uint64* flags, void** ptr, size_t* offset, size_t* start, size_t* select, size_t* disconnect, size_t* len, const char** addrspace) {
667 if (!SDL_Libretro_IsGameReady(lr) || index >= lr->core.memoryMapDescriptorCount) {
668 return false;
669 }
670 const struct retro_memory_descriptor* d = &lr->core.memoryMapDescriptors[index];
671 if (flags) *flags = d->flags;
672 if (ptr) *ptr = d->ptr;
673 if (offset) *offset = d->offset;
674 if (start) *start = d->start;
675 if (select) *select = d->select;
676 if (disconnect) *disconnect = d->disconnect;
677 if (len) *len = d->len;
678 if (addrspace) *addrspace = d->addrspace;
679 return true;
680}
681
704void* SDL_Libretro_GetMapAddress(const SDL_Libretro* lr, size_t address, size_t* regionRemaining) {
705 if (regionRemaining) *regionRemaining = 0;
706 if (!SDL_Libretro_IsGameReady(lr)) return NULL;
707
708 for (unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
709 const struct retro_memory_descriptor* d = &lr->core.memoryMapDescriptors[i];
710 if (!d->ptr || d->len == 0) continue;
711
712 // Does this descriptor's address space contain `address`?
713 if (d->select != 0) {
714 if (((address ^ d->start) & d->select) != 0) continue;
715 }
716 else if (address < d->start || (address - d->start) >= d->len) {
717 continue;
718 }
719
720 // Fold out disconnected bits, then offset into the host buffer.
721 size_t masked = address & ~d->disconnect;
722 if (masked < d->start) continue;
723 size_t within = masked - d->start;
724 if (within >= d->len) continue;
725
726 if (regionRemaining) *regionRemaining = d->len - within;
727 return (Uint8*)d->ptr + d->offset + within;
728 }
729 return NULL;
730}
731
736static const char* SDL_Libretro_GetMemoryTypeName(unsigned memoryType) {
737 switch (memoryType) {
738 case RETRO_MEMORY_SAVE_RAM: return "SRAM";
739 case RETRO_MEMORY_RTC: return "RTC";
740 case RETRO_MEMORY_SYSTEM_RAM: return "system RAM";
741 case RETRO_MEMORY_VIDEO_RAM: return "video RAM";
742 default: return "memory";
743 }
744}
745
757bool SDL_Libretro_SaveMemory_IO(SDL_Libretro* lr, unsigned memoryType, SDL_IOStream* dst, bool closeio) {
758 bool ok = false;
759 if (!lr || !lr->core.gameLoaded || !dst) {
760 SDL_SetError("[SDL_Libretro] Invalid SaveMemory_IO arguments");
761 }
762 else {
763 size_t size = 0;
764 void* mem = SDL_Libretro_GetMemoryData(lr, memoryType, &size);
765 if (!mem || size == 0) {
766 ok = true; // core has no such memory; nothing to save
767 }
768 else {
769 ok = (SDL_WriteIO(dst, mem, size) == size);
770 }
771 }
772 if (closeio && dst) SDL_CloseIO(dst);
773 return ok;
774}
775
787bool SDL_Libretro_SaveMemory(SDL_Libretro* lr, unsigned memoryType, const char* file) {
788 if (!lr || !lr->core.gameLoaded || !file) {
789 SDL_SetError("[SDL_Libretro] Invalid SaveMemory arguments");
790 return false;
791 }
792
793 size_t size = 0;
794 void* mem = SDL_Libretro_GetMemoryData(lr, memoryType, &size);
795 // Nothing to save, so don't create an empty file.
796 if (!mem || size == 0) return true;
797
798 char tempPath[SDL_LIBRETRO_MAX_PATH];
799 SDL_IOStream* io = SDL_Libretro_OpenSaveFile(file, tempPath, sizeof(tempPath));
800 if (!io) return false;
801 bool ok = SDL_Libretro_CommitSaveFile(io, SDL_Libretro_SaveMemory_IO(lr, memoryType, io, false), tempPath, file);
802 if (ok) {
803 SDL_Log("[SDL_Libretro] %s saved to %s (%zu bytes)", SDL_Libretro_GetMemoryTypeName(memoryType), file, size);
804 }
805 return ok;
806}
807
822bool SDL_Libretro_LoadMemory_IO(SDL_Libretro* lr, unsigned memoryType, SDL_IOStream* src, bool closeio) {
823 if (!lr || !lr->core.gameLoaded || !src) {
824 SDL_SetError("[SDL_Libretro] Invalid LoadMemory_IO arguments");
825 if (closeio && src) SDL_CloseIO(src);
826 return false;
827 }
828
829 // Bail before reading the stream when there's nowhere to put the data.
830 size_t capacity = 0;
831 if (!SDL_Libretro_GetMemoryData(lr, memoryType, &capacity) || capacity == 0) {
832 SDL_SetError("[SDL_Libretro] Core has no %s", SDL_Libretro_GetMemoryTypeName(memoryType));
833 if (closeio) SDL_CloseIO(src);
834 return false;
835 }
836
837 size_t fileSize = 0;
838 void* data = SDL_LoadFile_IO(src, &fileSize, closeio);
839 if (!data) return false;
840
841 // A size mismatch is the usual cause of a save that loads only partially.
842 if (fileSize != capacity) {
843 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] %s size mismatch (file %zu bytes, region %zu bytes); loading %zu", SDL_Libretro_GetMemoryTypeName(memoryType), fileSize, capacity, fileSize < capacity ? fileSize : capacity);
844 }
845
846 // SetMemoryData clamps to the region capacity.
847 bool ok = SDL_Libretro_SetMemoryData(lr, memoryType, data, fileSize);
848 SDL_free(data);
849 return ok;
850}
851
862bool SDL_Libretro_LoadMemory(SDL_Libretro* lr, unsigned memoryType, const char* file) {
863 if (!lr || !lr->core.gameLoaded || !file) {
864 SDL_SetError("[SDL_Libretro] Invalid LoadMemory arguments");
865 return false;
866 }
867 SDL_IOStream* io = SDL_IOFromFile(file, "rb");
868 if (!io) return false;
869 bool ok = SDL_Libretro_LoadMemory_IO(lr, memoryType, io, true);
870 if (ok) {
871 SDL_Log("[SDL_Libretro] %s loaded from %s", SDL_Libretro_GetMemoryTypeName(memoryType), file);
872 }
873 return ok;
874}
875
876// SRAM convenience wrappers (RETRO_MEMORY_SAVE_RAM)
877
893void SDL_Libretro_SetSRAMAutoSave(SDL_Libretro* lr, bool enabled) {
894 if (lr) lr->sramAutoSave = enabled;
895}
896
905bool SDL_Libretro_GetSRAMAutoSave(const SDL_Libretro* lr) {
906 return lr && lr->sramAutoSave;
907}
908
913static void SDL_Libretro_AutoLoadSRAM(SDL_Libretro* lr) {
914 char path[SDL_LIBRETRO_MAX_PATH];
915 if (!lr->sramAutoSave || SDL_Libretro_GetSavePath(lr, ".srm", path, sizeof(path)) == 0 || !SDL_GetPathInfo(path, NULL)) return;
916 if (!SDL_Libretro_LoadSRAM(lr, path)) {
917 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Failed to load SRAM: %s", SDL_GetError());
918 }
919}
920
925static void SDL_Libretro_AutoSaveSRAM(SDL_Libretro* lr) {
926 char path[SDL_LIBRETRO_MAX_PATH];
927 if (!lr->sramAutoSave || !lr->core.gameLoaded || SDL_Libretro_GetSavePath(lr, ".srm", path, sizeof(path)) == 0) return;
928 if (!SDL_Libretro_SaveSRAM(lr, path)) {
929 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Failed to save SRAM: %s", SDL_GetError());
930 }
931}
932
945bool SDL_Libretro_SaveSRAM_IO(SDL_Libretro* lr, SDL_IOStream* dst, bool closeio) {
946 return SDL_Libretro_SaveMemory_IO(lr, RETRO_MEMORY_SAVE_RAM, dst, closeio);
947}
948
960bool SDL_Libretro_SaveSRAM(SDL_Libretro* lr, const char* file) {
961 return SDL_Libretro_SaveMemory(lr, RETRO_MEMORY_SAVE_RAM, file);
962}
963
976bool SDL_Libretro_LoadSRAM_IO(SDL_Libretro* lr, SDL_IOStream* src, bool closeio) {
977 return SDL_Libretro_LoadMemory_IO(lr, RETRO_MEMORY_SAVE_RAM, src, closeio);
978}
979
991bool SDL_Libretro_LoadSRAM(SDL_Libretro* lr, const char* file) {
992 return SDL_Libretro_LoadMemory(lr, RETRO_MEMORY_SAVE_RAM, file);
993}
994
995// Rewind
996
997#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
998
1012static size_t SDL_Libretro_RewindMaxEncodedSize(size_t len) {
1013 return len + (len / 128) + 1;
1014}
1015
1024static size_t SDL_Libretro_RewindMatchRun(const unsigned char* a, const unsigned char* b, size_t max) {
1025 size_t i = 0;
1026 for (; i + sizeof(size_t) <= max; i += sizeof(size_t)) {
1027 size_t wa, wb;
1028 SDL_memcpy(&wa, a + i, sizeof(size_t));
1029 SDL_memcpy(&wb, b + i, sizeof(size_t));
1030 if (wa != wb) break;
1031 }
1032 while (i < max && a[i] == b[i]) i++;
1033 return i;
1034}
1035
1049static size_t SDL_Libretro_RewindEncodeDelta(const unsigned char* cur, const unsigned char* ref, size_t len, unsigned char* out, size_t outCap) {
1050 // A matching gap shorter than this is cheaper to fold into a literal than to emit as a skip. A skip also forces a new literal header afterwards.
1051 const size_t kMinSkip = 3;
1052
1053 size_t op = 0, i = 0;
1054 while (i < len) {
1055 if (cur[i] == ref[i]) {
1056 size_t run = SDL_Libretro_RewindMatchRun(cur + i, ref + i, len - i);
1057 i += run;
1058 while (run > 0) {
1059 if (run <= 127) {
1060 if (out) {
1061 if (op >= outCap) return 0;
1062 out[op] = (unsigned char)run;
1063 }
1064 op++;
1065 run = 0;
1066 }
1067 else if (run < 255) {
1068 // Two short skips (2 bytes) beat one extended skip (3)
1069 if (out) {
1070 if (op >= outCap) return 0;
1071 out[op] = 127;
1072 }
1073 op++;
1074 run -= 127;
1075 }
1076 else {
1077 size_t chunk = run > 65535 ? 65535 : run;
1078 if (out) {
1079 if (op + 3 > outCap) return 0;
1080 out[op] = 0x00;
1081 out[op + 1] = (unsigned char)(chunk & 0xFF);
1082 out[op + 2] = (unsigned char)(chunk >> 8);
1083 }
1084 op += 3;
1085 run -= chunk;
1086 }
1087 }
1088 }
1089 else {
1090 // Literal segment: a span of differing bytes that absorbs any matching gaps shorter than kMinSkip (folded as zero XOR bytes).
1091 size_t segStart = i;
1092 size_t segEnd = i;
1093 size_t j = i;
1094 for (;;) {
1095 while (j < len && cur[j] != ref[j]) j++;
1096 segEnd = j;
1097 if (j >= len) break;
1098 size_t gapStart = j;
1099 j += SDL_Libretro_RewindMatchRun(cur + j, ref + j, len - j);
1100 if ((j - gapStart) >= kMinSkip || j >= len) break;
1101 }
1102 i = segEnd;
1103 for (size_t pos = segStart; pos < segEnd;) {
1104 size_t chunk = segEnd - pos;
1105 if (chunk > 128) chunk = 128;
1106 if (out) {
1107 if (op + 1 + chunk > outCap) return 0;
1108 out[op] = (unsigned char)(0x80 | (chunk - 1));
1109 }
1110 op++;
1111 for (size_t k = pos; k < pos + chunk; k++) {
1112 if (out) out[op] = cur[k] ^ ref[k];
1113 op++;
1114 }
1115 pos += chunk;
1116 }
1117 }
1118 }
1119 return op;
1120}
1121
1131static bool SDL_Libretro_RewindDecodeDelta(const unsigned char* delta, size_t deltaLen, unsigned char* state, size_t stateLen) {
1132 size_t dp = 0, sp = 0;
1133 while (dp < deltaLen && sp < stateLen) {
1134 unsigned char tag = delta[dp++];
1135 if (tag == 0x00) {
1136 if (dp + 2 > deltaLen) return false;
1137 size_t skip = delta[dp] | ((size_t)delta[dp + 1] << 8);
1138 dp += 2;
1139 sp += skip;
1140 }
1141 else if (tag <= 0x7F) {
1142 sp += tag;
1143 }
1144 else {
1145 size_t count = (tag & 0x7F) + 1;
1146 if (dp + count > deltaLen || sp + count > stateLen) return false;
1147 for (size_t j = 0; j < count; j++)
1148 state[sp++] ^= delta[dp++];
1149 }
1150 }
1151 return (sp <= stateLen);
1152}
1153
1154#endif /* SDL_LIBRETRO_ENABLE_REWIND_DELTA */
1155
1164static bool SDL_Libretro_RewindAllocate(SDL_Libretro* lr) {
1165 size_t slotSize = lr->core.symbols.retro_serialize_size();
1166 if (slotSize == 0) {
1167 SDL_SetError("[SDL_Libretro] Core does not support serialization");
1168 return false;
1169 }
1170
1171 // Cores that flag their state as incomplete warn the frontend not to rely on it for frame-sensitive features (netplay, rerecording).
1172 if (lr->core.serializationQuirks & RETRO_SERIALIZATION_QUIRK_INCOMPLETE) {
1173 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SDL_Libretro] Core reports incomplete serialization, so rewind may be unreliable");
1174 }
1175
1176 // Initialize the rewind slots.
1177 unsigned char* ref = (unsigned char*)SDL_calloc(1, slotSize);
1178 unsigned char* scratch = (unsigned char*)SDL_malloc(slotSize);
1179 SDL_LibretroRewindDelta* entries = (SDL_LibretroRewindDelta*)SDL_calloc(lr->rewindCapacity, sizeof(*entries));
1180 unsigned char* encScratch = NULL;
1181 bool ok = (ref && scratch && entries);
1182#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1183 // Delta mode keeps a reusable worst-case-sized buffer for single-pass encoding. Full-state mode stores raw snapshots and needs none.
1184 encScratch = (unsigned char*)SDL_malloc(SDL_Libretro_RewindMaxEncodedSize(slotSize));
1185 ok = ok && (encScratch != NULL);
1186#endif
1187 if (!ok) {
1188 SDL_free(ref);
1189 SDL_free(scratch);
1190 SDL_free(encScratch);
1191 SDL_free(entries);
1192 SDL_SetError("[SDL_Libretro] Failed to allocate rewind buffers");
1193 return false;
1194 }
1195
1196 // Set the initial state.
1197 lr->rewindReference = ref;
1198 lr->rewindScratch = scratch;
1199 lr->rewindEncodeScratch = encScratch; // NULL in full-state mode
1200 lr->rewindEntries = entries;
1201 lr->rewindSlotSize = slotSize;
1202 lr->rewindBytes = 0;
1203 lr->rewindHead = 0;
1204 lr->rewindCount = 0;
1205 lr->rewindFrameCounter = 0;
1206 lr->rewindHasReference = false;
1207 lr->rewindActive = false;
1208 return true;
1209}
1210
1227bool SDL_Libretro_SetRewindEnabled(SDL_Libretro* lr, bool enabled, unsigned bufferFrames, unsigned captureInterval) {
1228 if (!lr) {
1229 return SDL_InvalidParamError("lr");
1230 }
1231
1232 SDL_Libretro_RewindFree(lr);
1233
1234 if (!enabled) {
1235 lr->rewindEnabled = false;
1236 // Without rewind a negative speed would leave the game stuck.
1237 if (lr->core.speed < 0.0f) {
1238 SDL_Libretro_SetSpeed(lr, 1.0f);
1239 }
1240 return true;
1241 }
1242
1243 // Sane defaults.
1244 if (bufferFrames == 0) bufferFrames = 300;
1245 if (captureInterval == 0) captureInterval = 1;
1246
1247 lr->rewindEnabled = true;
1248 lr->rewindCapacity = bufferFrames;
1249 lr->rewindCaptureInterval = captureInterval;
1250
1251 // The serialize size is only defined while a game is loaded, so the
1252 // buffer waits for one.
1253 if (!SDL_Libretro_IsGameReady(lr)) {
1254 return true;
1255 }
1256 return SDL_Libretro_RewindAllocate(lr);
1257}
1258
1265bool SDL_Libretro_GetRewindEnabled(const SDL_Libretro* lr) {
1266 return lr && lr->rewindEnabled;
1267}
1268
1275double SDL_Libretro_GetRewindRemaining(const SDL_Libretro* lr) {
1276 if (!lr || !lr->rewindEnabled || lr->rewindCount == 0) return 0.0;
1277 double fps = lr->core.fps > 0.0 ? lr->core.fps : 60.0;
1278 return (double)lr->rewindCount * (double)lr->rewindCaptureInterval / fps;
1279}
1280
1290size_t SDL_Libretro_GetRewindMemoryUsage(const SDL_Libretro* lr) {
1291 if (!lr || !lr->rewindEnabled) return 0;
1292 size_t total = lr->rewindBytes;
1293 if (lr->rewindReference) total += lr->rewindSlotSize;
1294 if (lr->rewindScratch) total += lr->rewindSlotSize;
1295 total += (size_t)lr->rewindCapacity * sizeof(SDL_LibretroRewindDelta);
1296 return total;
1297}
1298
1310void SDL_Libretro_SetRewindMemoryLimit(SDL_Libretro* lr, size_t maxBytes) {
1311 if (!lr) return;
1312 lr->rewindMaxBytes = maxBytes;
1313 SDL_Libretro_RewindEvictToBudget(lr);
1314}
1315
1322size_t SDL_Libretro_GetRewindMemoryLimit(const SDL_Libretro* lr) {
1323 return lr ? lr->rewindMaxBytes : 0;
1324}
1325
1342bool SDL_Libretro_SetRewindMemoryDuration(SDL_Libretro* lr, double seconds) {
1343 if (!lr) {
1344 return SDL_InvalidParamError("lr");
1345 }
1346 if (!(seconds > 0.0)) {
1347 SDL_SetError("[SDL_Libretro] Rewind duration must be positive");
1348 return false;
1349 }
1350
1351 // Per-snapshot worst-case size. Prefer the size the rewind buffer is already
1352 // using; otherwise query the core directly so this works before rewind is enabled.
1353 size_t slotSize = lr->rewindSlotSize;
1354 if (slotSize == 0 && SDL_Libretro_IsCoreReady(lr) && lr->core.symbols.retro_serialize_size) {
1355 slotSize = lr->core.symbols.retro_serialize_size();
1356 }
1357 if (slotSize == 0) {
1358 SDL_SetError("[SDL_Libretro] Rewind state size unknown; load a serializable core first");
1359 return false;
1360 }
1361
1362 double fps = (lr->core.fps > 0.0) ? lr->core.fps : 60.0;
1363 unsigned interval = (lr->rewindCaptureInterval > 0) ? lr->rewindCaptureInterval : 1;
1364
1365 // Number of snapshots that cover the requested span, rounded up so the budget
1366 // never falls short of the duration.
1367 double snapshots = (seconds * fps) / (double)interval;
1368 if (snapshots < 1.0) snapshots = 1.0;
1369 size_t snaps = (size_t)snapshots;
1370 if ((double)snaps < snapshots) snaps++;
1371
1372 SDL_Libretro_SetRewindMemoryLimit(lr, snaps * slotSize);
1373 return true;
1374}
1375
1380static void SDL_Libretro_RewindCapture(SDL_Libretro* lr) {
1381 if (!lr->rewindEnabled) return;
1382
1383 // Some cores report no state size until they've run a frame
1384 // (RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE), so allocate once they do.
1385 if (!lr->rewindReference) {
1386 if (lr->core.symbols.retro_serialize_size() == 0 || !SDL_Libretro_RewindAllocate(lr)) return;
1387 }
1388
1389 lr->rewindFrameCounter++;
1390 if (lr->rewindFrameCounter < lr->rewindCaptureInterval) return;
1391 lr->rewindFrameCounter = 0;
1392
1393 // A core may change its serialize size mid-session
1394 // (RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE). The reference/scratch
1395 // buffers and every stored delta are sized to the old state, so resize the
1396 // working buffers and drop the now-incompatible history before continuing.
1397 // Without this, retro_serialize() into an undersized scratch fails and rewind
1398 // silently stops recording.
1399 size_t curSize = lr->core.symbols.retro_serialize_size();
1400 if (curSize == 0) return;
1401 if (curSize != lr->rewindSlotSize) {
1402 unsigned char* nref = (unsigned char*)SDL_calloc(1, curSize);
1403 unsigned char* nscr = (unsigned char*)SDL_malloc(curSize);
1404 unsigned char* nenc = NULL;
1405 bool ok = (nref && nscr);
1406#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1407 nenc = (unsigned char*)SDL_malloc(SDL_Libretro_RewindMaxEncodedSize(curSize));
1408 ok = ok && (nenc != NULL);
1409#endif
1410 if (!ok) {
1411 SDL_free(nref);
1412 SDL_free(nscr);
1413 SDL_free(nenc);
1414 return;
1415 }
1416 SDL_free(lr->rewindReference);
1417 SDL_free(lr->rewindScratch);
1418 SDL_free(lr->rewindEncodeScratch);
1419 lr->rewindReference = nref;
1420 lr->rewindScratch = nscr;
1421 lr->rewindEncodeScratch = nenc; // NULL in full-state mode
1422 lr->rewindSlotSize = curSize;
1423 SDL_Libretro_ClearRewind(lr);
1424 }
1425
1426 if (!lr->core.symbols.retro_serialize(lr->rewindScratch, lr->rewindSlotSize)) return;
1427
1428 if (!lr->rewindHasReference) {
1429 SDL_memcpy(lr->rewindReference, lr->rewindScratch, lr->rewindSlotSize);
1430 lr->rewindHasReference = true;
1431 return;
1432 }
1433
1434 // Store the step needed to walk back to the previous state. Both modes keep
1435 // `reference` holding the newest state and the slot holding the previous one,
1436 // so the head always represents "now" and step-back semantics are identical.
1437 SDL_LibretroRewindDelta* slot = &lr->rewindEntries[lr->rewindHead];
1438#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1439 // Delta mode: encode the change between the new state (scratch) and the
1440 // previous one (reference) once into the reusable worst-case-sized buffer,
1441 // then copy just the produced bytes into the slot. The old approach ran the
1442 // encoder twice (a NULL pass to size, then a real pass), scanning the whole
1443 // state both times; for multi-megabyte states (e.g. PSX) that second pass
1444 // dominated capture cost. The copy here is only of the compressed delta.
1445 size_t storeSize = SDL_Libretro_RewindEncodeDelta(
1446 lr->rewindScratch,
1447 lr->rewindReference,
1448 lr->rewindSlotSize,
1449 lr->rewindEncodeScratch,
1450 SDL_Libretro_RewindMaxEncodedSize(lr->rewindSlotSize));
1451 if (storeSize == 0) return;
1452
1453 // Reuse the slot's existing allocation when it's already big enough; only (re)allocate when the snapshot needs more room. At steady state this stops allocating entirely, avoiding a malloc/free on every captured frame.
1454 //
1455 // rewindBytes tracks allocated snapshot memory (capacity), so it stays accurate whether or not a slot is overwritten in place.
1456 if (slot->capacity < storeSize) {
1457 unsigned char* data = (unsigned char*)SDL_realloc(slot->data, storeSize);
1458 if (!data) return;
1459 lr->rewindBytes += storeSize - slot->capacity;
1460 slot->data = data;
1461 slot->capacity = storeSize;
1462 }
1463 SDL_memcpy(slot->data, lr->rewindEncodeScratch, storeSize);
1464 slot->length = storeSize;
1465
1466 // Ping-pong: the freshly serialized state in `scratch` becomes the new reference, and the now-stale reference buffer is recycled as next frame's scratch (overwritten by the next retro_serialize). Swapping pointers avoids a full state-sized memcpy on every captured frame.
1467 unsigned char* swap = lr->rewindReference;
1468 lr->rewindReference = lr->rewindScratch;
1469 lr->rewindScratch = swap;
1470#else
1471 // Full-state mode. The slot must hold the previous state verbatim, which is exactly what `reference` already contains. Rather than memcpy a multi-MB state into the slot every frame, donate the reference buffer to the slot and recycle the slot's old (evicted) buffer as the next scratch, a few pointer assignments instead of a full state-sized copy. The new state in `scratch` becomes the reference. Both buffers are sized to slotSize, so in steady state no allocation happens at all.
1472 unsigned char* recycled = slot->data; // old state being evicted from this slot (NULL until first wrap)
1473 size_t recycledCap = slot->capacity;
1474 if (recycledCap < lr->rewindSlotSize) {
1475 // Slot's buffer can't serve as a full-state scratch; grow it before mutating anything so a failure leaves the ring and buffers untouched.
1476 unsigned char* grown = (unsigned char*)SDL_realloc(recycled, lr->rewindSlotSize);
1477 if (!grown) return;
1478 recycled = grown;
1479 recycledCap = lr->rewindSlotSize;
1480 }
1481
1482 lr->rewindBytes += lr->rewindSlotSize - slot->capacity;
1483 slot->data = lr->rewindReference; // holds the previous state
1484 slot->length = lr->rewindSlotSize;
1485 slot->capacity = lr->rewindSlotSize;
1486 lr->rewindReference = lr->rewindScratch; // holds the new state
1487 lr->rewindScratch = recycled;
1488#endif
1489
1490 lr->rewindHead = (lr->rewindHead + 1) % lr->rewindCapacity;
1491 if (lr->rewindCount < lr->rewindCapacity) {
1492 lr->rewindCount++;
1493 }
1494
1495 // Keep total delta memory under the configured budget by dropping the oldest snapshots; this bounds worst-case memory for large/incompressible states.
1496 SDL_Libretro_RewindEvictToBudget(lr);
1497}
1498
1503static void SDL_Libretro_RewindFreeEntry(SDL_Libretro* lr, SDL_LibretroRewindDelta* entry) {
1504 lr->rewindBytes -= entry->capacity;
1505 SDL_free(entry->data);
1506 entry->data = NULL;
1507 entry->length = 0;
1508 entry->capacity = 0;
1509}
1510
1517static bool SDL_Libretro_RewindStepState(SDL_Libretro* lr) {
1518 if (!lr->rewindEnabled || !lr->rewindReference || lr->rewindCount == 0) return false;
1519
1520 lr->rewindHead = (lr->rewindHead == 0) ? (lr->rewindCapacity - 1) : (lr->rewindHead - 1);
1521 lr->rewindCount--;
1522
1523 SDL_LibretroRewindDelta* entry = &lr->rewindEntries[lr->rewindHead];
1524 if (!entry->data || entry->length == 0) return false;
1525
1526#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1527 bool reconstructed = SDL_Libretro_RewindDecodeDelta(entry->data, entry->length, lr->rewindReference, lr->rewindSlotSize);
1528#else
1529 // Full-state mode: the entry is the previous state verbatim.
1530 bool reconstructed = (entry->length == lr->rewindSlotSize);
1531 if (reconstructed) SDL_memcpy(lr->rewindReference, entry->data, lr->rewindSlotSize);
1532#endif
1533
1534 SDL_Libretro_RewindFreeEntry(lr, entry);
1535
1536 // A failed reconstruction (a malformed or partial XOR decode) or a rejected
1537 // unserialize leaves `reference` out of sync with the state the core still
1538 // holds, and the remaining deltas are anchored to a reference we can no
1539 // longer rebuild. Discard the now-untrustworthy history so the next forward
1540 // capture re-seeds from a clean serialize instead of encoding the next delta
1541 // against a corrupt base.
1542 if (!reconstructed || !lr->core.symbols.retro_unserialize(lr->rewindReference, lr->rewindSlotSize)) {
1543 SDL_Libretro_ClearRewind(lr);
1544 return false;
1545 }
1546 return true;
1547}
1548
1558static bool SDL_Libretro_RewindStep(SDL_Libretro* lr) {
1559 if (!SDL_Libretro_IsGameReady(lr) || !lr->rewindEnabled) {
1560 SDL_SetError("[SDL_Libretro] Rewind is not enabled");
1561 return false;
1562 }
1563 if (!SDL_Libretro_RewindStepState(lr)) return false;
1564
1565 lr->rewindActive = true;
1566 lr->core.symbols.retro_run();
1567 lr->rewindActive = false;
1568 return true;
1569}
1570
1577static void SDL_Libretro_RewindEvictToBudget(SDL_Libretro* lr) {
1578 if (!lr->rewindEnabled || lr->rewindMaxBytes == 0 || !lr->rewindEntries) return;
1579
1580 while (lr->rewindBytes > lr->rewindMaxBytes && lr->rewindCount > 1) {
1581 unsigned tail = (lr->rewindHead + lr->rewindCapacity - lr->rewindCount) % lr->rewindCapacity;
1582 SDL_Libretro_RewindFreeEntry(lr, &lr->rewindEntries[tail]);
1583 lr->rewindCount--;
1584 }
1585}
1586
1595static void SDL_Libretro_ClearRewind(SDL_Libretro* lr) {
1596 if (!lr) return;
1597 if (lr->rewindEntries) {
1598 for (unsigned i = 0; i < lr->rewindCapacity; i++) {
1599 SDL_free(lr->rewindEntries[i].data);
1600 lr->rewindEntries[i].data = NULL;
1601 lr->rewindEntries[i].length = 0;
1602 lr->rewindEntries[i].capacity = 0;
1603 }
1604 }
1605 lr->rewindBytes = 0;
1606 lr->rewindHead = 0;
1607 lr->rewindCount = 0;
1608 lr->rewindFrameCounter = 0;
1609 lr->rewindHasReference = false;
1610}
1611
1616static void SDL_Libretro_RewindFree(SDL_Libretro* lr) {
1617 // Clear the rewind data.
1618 SDL_Libretro_ClearRewind(lr);
1619
1620 // Free all of the allocated memory.
1621 SDL_free(lr->rewindEntries);
1622 lr->rewindEntries = NULL;
1623 SDL_free(lr->rewindReference);
1624 lr->rewindReference = NULL;
1625 SDL_free(lr->rewindScratch);
1626 lr->rewindScratch = NULL;
1627 SDL_free(lr->rewindEncodeScratch);
1628 lr->rewindEncodeScratch = NULL;
1629 lr->rewindSlotSize = 0;
1630 lr->rewindActive = false;
1631}
1632
1633#endif /* SDL_LIBRETRO_SERIALIZE_IMPL_ONCE */
bool SDL_Libretro_SaveState_IO(SDL_Libretro *lr, SDL_IOStream *dst, bool closeio)
Save the current libretro state to the given SDL_IOStream.
bool SDL_Libretro_IsCoreReady(const SDL_Libretro *lr)
Indicates whether a core is loaded.
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.
void SDL_Libretro_SetRewindMemoryLimit(SDL_Libretro *lr, size_t maxBytes)
Set the maximum number of bytes of encoded delta history to retain.
bool SDL_Libretro_SaveSRAM(SDL_Libretro *lr, const char *file)
Saves the current SRAM to the given file.
bool SDL_Libretro_SetCheat(SDL_Libretro *lr, unsigned index, bool enabled, const char *code)
Set a cheat code either enabled or disabled within the libretro context.
bool SDL_Libretro_GetSRAMAutoSave(const SDL_Libretro *lr)
Check whether SRAM is loaded and saved along with the game.
bool SDL_Libretro_AddDiskImage(SDL_Libretro *lr, const char *path)
Appends a new disk image from a file path.
bool SDL_Libretro_GetMemoryMapDescriptor(const SDL_Libretro *lr, unsigned index, Uint64 *flags, void **ptr, size_t *offset, size_t *start, size_t *select, size_t *disconnect, size_t *len, const char **addrspace)
Retrieve one memory-map descriptor by index.
bool SDL_Libretro_EjectDisk(SDL_Libretro *lr)
Ejects the disk tray, allowing the disk image to be changed.
bool SDL_Libretro_LoadSRAM(SDL_Libretro *lr, const char *file)
Loads the SRAM from the given file.
void SDL_Libretro_ResetCheats(SDL_Libretro *lr)
Clears all cheats that were set on the core.
void * SDL_Libretro_GetMemoryData(const SDL_Libretro *lr, unsigned memoryType, size_t *size)
Get a pointer to a core memory region and its size.
bool SDL_Libretro_SetRewindEnabled(SDL_Libretro *lr, bool enabled, unsigned bufferFrames, unsigned captureInterval)
Enable or disable the rewind system.
bool SDL_Libretro_SetRewindMemoryDuration(SDL_Libretro *lr, double seconds)
Set the rewind memory budget by target duration instead of raw bytes.
size_t SDL_Libretro_GetRewindMemoryUsage(const SDL_Libretro *lr)
Get the approximate memory currently held by the rewind buffer, in bytes.
bool SDL_Libretro_SaveSRAM_IO(SDL_Libretro *lr, SDL_IOStream *dst, bool closeio)
Write the core's battery-backed save RAM to a stream.
unsigned SDL_Libretro_GetMemoryMapCount(const SDL_Libretro *lr)
Get the number of memory-map descriptors the core has published.
bool SDL_Libretro_SetInitialDisk(SDL_Libretro *lr, unsigned index, const char *path)
Sets the disk image to insert once content is loaded.
bool SDL_Libretro_LoadSRAM_IO(SDL_Libretro *lr, SDL_IOStream *src, bool closeio)
Load the core's battery-backed save RAM from a stream.
unsigned SDL_Libretro_GetDiskIndex(const SDL_Libretro *lr)
Returns the index of the currently inserted disk image.
bool SDL_Libretro_SetDiskIndex(SDL_Libretro *lr, unsigned index)
Sets the disk image index to insert into the emulated drive.
bool SDL_Libretro_LoadState(SDL_Libretro *lr, const char *file)
Loads the libretro state from the given file.
bool SDL_Libretro_AddDiskImage_IO(SDL_Libretro *lr, SDL_IOStream *src, const char *path, bool closeio)
Appends a new disk image from an SDL_IOStream.
size_t SDL_Libretro_GetStateSize(const SDL_Libretro *lr)
Retrieves the size of serialized states.
bool SDL_Libretro_SaveMemory(SDL_Libretro *lr, unsigned memoryType, const char *file)
Write a core memory region to a file.
bool SDL_Libretro_LoadState_IO(SDL_Libretro *lr, SDL_IOStream *src, bool closeio)
Load a libretro state from the given SDL_IOStream.
bool SDL_Libretro_GetDiskLabel(const SDL_Libretro *lr, unsigned index, char *label, size_t len)
Retrieves the human-readable label of a disk image.
void SDL_Libretro_SetSRAMAutoSave(SDL_Libretro *lr, bool enabled)
Load and save the core's battery-backed save RAM along with the game.
bool SDL_Libretro_LoadMemory_IO(SDL_Libretro *lr, unsigned memoryType, SDL_IOStream *src, bool closeio)
Load a core memory region from a stream.
bool SDL_Libretro_LoadMemory(SDL_Libretro *lr, unsigned memoryType, const char *file)
Load a core memory region from a file.
bool SDL_Libretro_GetRewindEnabled(const SDL_Libretro *lr)
Check whether the rewind system is enabled (independent of current direction).
unsigned SDL_Libretro_GetDiskCount(const SDL_Libretro *lr)
Returns the number of disk images available to the loaded core.
bool SDL_Libretro_SaveState(SDL_Libretro *lr, const char *file)
Saves the current libretro state to a file.
size_t SDL_Libretro_GetRewindMemoryLimit(const SDL_Libretro *lr)
Get the current rewind memory budget in bytes (0 if unbounded).
bool SDL_Libretro_SetMemoryData(SDL_Libretro *lr, unsigned memoryType, const void *data, size_t size)
Overwrite a core memory region with caller-provided bytes.
double SDL_Libretro_GetRewindRemaining(const SDL_Libretro *lr)
Calculates the amount of rewind time remaining in the buffer.
bool SDL_Libretro_IsGameReady(const SDL_Libretro *lr)
Indicates whether a game is loaded and running.
bool SDL_Libretro_InsertDisk(SDL_Libretro *lr)
Closes the disk tray after a disk change.
void * SDL_Libretro_GetMapAddress(const SDL_Libretro *lr, size_t address, size_t *regionRemaining)
Translate an emulated (guest) address to a live host pointer via the memory map.
bool SDL_Libretro_SaveMemory_IO(SDL_Libretro *lr, unsigned memoryType, SDL_IOStream *dst, bool closeio)
Write a core memory region to a stream.
void SDL_Libretro_SetSpeed(SDL_Libretro *lr, float speed)
Sets how fast the game runs, as a multiple of its normal speed.