7#if defined(SDL_LIBRETRO_IMPLEMENTATION) && !defined(SDL_LIBRETRO_SERIALIZE_IMPL_ONCE)
8#define SDL_LIBRETRO_SERIALIZE_IMPL_ONCE
21#define SDL_LIBRETRO_ENABLE_REWIND_DELTA
25#ifndef SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES
31#define SDL_LIBRETRO_REWIND_DEFAULT_MAX_BYTES ((size_t)256 * 1024 * 1024)
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);
54 return SDL_IOFromFile(tempPath,
"wb");
69static bool SDL_Libretro_CommitSaveFile(SDL_IOStream* io,
bool written,
const char* tempPath,
const char* file) {
72 bool ok = written && SDL_GetIOStatus(io) != SDL_IO_STATUS_ERROR;
73 ok = SDL_CloseIO(io) && ok;
74 if (ok && !SDL_RenamePath(tempPath, file)) {
78 SDL_RemovePath(tempPath);
93 return lr->core.disk_control.get_num_images();
104 return lr->core.disk_control.get_image_index();
118 SDL_SetError(
"[SDL_Libretro] Disk control not available");
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");
125 return lr->core.disk_control.set_image_index(index);
136 SDL_SetError(
"[SDL_Libretro] Disk control not available");
139 return lr->core.disk_control.set_eject_state(
true);
150 SDL_SetError(
"[SDL_Libretro] Disk control not available");
153 return lr->core.disk_control.set_eject_state(
false);
164static void SDL_Libretro_FreeDiskImages(SDL_Libretro* lr) {
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);
171 SDL_free(image->path);
172 SDL_free(image->data);
174 SDL_free(lr->core.diskImages);
175 lr->core.diskImages = NULL;
176 lr->core.diskImageCount = 0;
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");
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");
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");
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");
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");
239 SDL_SetError(
"[SDL_Libretro] Invalid AddDiskImage arguments");
242 if (!SDL_Libretro_CanAddDiskImage(lr)) {
245 if (!SDL_Libretro_ContentNeedsFullpath(lr, SDL_Libretro_GetExtension(path))) {
247 SDL_IOStream* src = SDL_IOFromFile(path,
"rb");
249 SDL_SetError(
"[SDL_Libretro] Failed to open disk image '%s'", path);
254 struct retro_game_info info;
255 SDL_memset(&info, 0,
sizeof(info));
257 return SDL_Libretro_AppendDiskImage(lr, &info);
282 SDL_SetError(
"[SDL_Libretro] Invalid AddDiskImage_IO arguments");
283 if (closeio && src) SDL_CloseIO(src);
286 if (!SDL_Libretro_CanAddDiskImage(lr)) {
287 if (closeio) SDL_CloseIO(src);
292 SDL_LibretroDiskImage* images = (SDL_LibretroDiskImage*)SDL_realloc(lr->core.diskImages, (lr->core.diskImageCount + 1) *
sizeof(SDL_LibretroDiskImage));
294 if (closeio) SDL_CloseIO(src);
297 lr->core.diskImages = images;
298 SDL_LibretroDiskImage image = {0};
299 struct retro_game_info info;
300 SDL_memset(&info, 0,
sizeof(info));
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))) {
310 image.path = SDL_strdup(tempPath);
312 SDL_RemovePath(tempPath);
317 image.data = SDL_LoadFile_IO(src, &info.size, closeio);
318 if (!image.data)
return false;
319 image.path = SDL_strdup(path);
321 SDL_free(image.data);
325 info.path = image.path;
326 info.data = image.data;
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);
334 lr->core.diskImages[lr->core.diskImageCount++] = image;
351 SDL_SetError(
"[SDL_Libretro] Invalid GetDiskLabel arguments");
354 if (!lr->core.disk_control.get_image_label) {
355 SDL_SetError(
"[SDL_Libretro] Disk labels not available");
359 return lr->core.disk_control.get_image_label(index, label, len);
375 SDL_SetError(
"[SDL_Libretro] Invalid SetInitialDisk arguments");
378 if (!lr->core.disk_control.set_initial_image) {
379 SDL_SetError(
"[SDL_Libretro] Disk control not available");
382 return lr->core.disk_control.set_initial_image(index, path);
402 SDL_SetError(
"[SDL_Libretro] Invalid SetCheat arguments");
405 lr->core.symbols.retro_cheat_set(index, enabled, code);
420 lr->core.symbols.retro_cheat_reset();
433 return lr->core.symbols.retro_serialize_size();
450 SDL_SetError(
"[SDL_Libretro] Invalid SaveState_IO arguments");
453 size_t size = lr->core.symbols.retro_serialize_size();
455 SDL_SetError(
"[SDL_Libretro] Core does not support save states");
458 void* data = SDL_malloc(size);
460 SDL_SetError(
"[SDL_Libretro] Failed to allocate save state buffer");
463 if (lr->core.symbols.retro_serialize(data, size)) {
465 ok = (SDL_WriteIO(dst, data, size) == size);
468 SDL_SetError(
"[SDL_Libretro] Core failed to serialize state");
474 if (closeio && dst) SDL_CloseIO(dst);
490 SDL_SetError(
"[SDL_Libretro] Invalid SaveState arguments");
494 char tempPath[SDL_LIBRETRO_MAX_PATH];
495 SDL_IOStream* io = SDL_Libretro_OpenSaveFile(file, tempPath,
sizeof(tempPath));
496 if (!io)
return false;
516 SDL_SetError(
"[SDL_Libretro] Invalid LoadState_IO arguments");
517 if (closeio && src) SDL_CloseIO(src);
521 void* data = SDL_LoadFile_IO(src, &size, closeio);
522 if (!data)
return false;
525 SDL_SetError(
"[SDL_Libretro] Save state is empty");
527 else if (!lr->core.symbols.retro_unserialize(data, size)) {
528 SDL_SetError(
"[SDL_Libretro] Core rejected save state");
535 if (ok && lr->rewindEnabled) SDL_Libretro_ClearRewind(lr);
551 SDL_SetError(
"[SDL_Libretro] Invalid LoadState arguments");
554 SDL_IOStream* io = SDL_IOFromFile(file,
"rb");
555 if (!io)
return false;
574 if (!
SDL_Libretro_IsGameReady(lr) || !lr->core.symbols.retro_get_memory_data || !lr->core.symbols.retro_get_memory_size) {
578 void* ptr = lr->core.symbols.retro_get_memory_data(memoryType);
579 if (size) *size = lr->core.symbols.retro_get_memory_size(memoryType);
598 SDL_SetError(
"[SDL_Libretro] Invalid SetMemoryData arguments");
603 if (!dst || capacity == 0) {
604 SDL_SetError(
"[SDL_Libretro] Memory type %u unavailable", memoryType);
607 size_t copySize = size < capacity ? size : capacity;
608 SDL_memcpy(dst, data, copySize);
617static void SDL_Libretro_FreeMemoryMap(SDL_Libretro* lr) {
619 if (lr->core.memoryMapDescriptors) {
620 for (
unsigned i = 0; i < lr->core.memoryMapDescriptorCount; i++) {
621 SDL_free((
void*)lr->core.memoryMapDescriptors[i].addrspace);
623 SDL_free(lr->core.memoryMapDescriptors);
624 lr->core.memoryMapDescriptors = NULL;
626 lr->core.memoryMapDescriptorCount = 0;
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) {
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;
705 if (regionRemaining) *regionRemaining = 0;
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;
713 if (d->select != 0) {
714 if (((address ^ d->start) & d->select) != 0)
continue;
716 else if (address < d->start || (address - d->start) >= d->len) {
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;
726 if (regionRemaining) *regionRemaining = d->len - within;
727 return (Uint8*)d->ptr + d->offset + within;
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";
759 if (!lr || !lr->core.gameLoaded || !dst) {
760 SDL_SetError(
"[SDL_Libretro] Invalid SaveMemory_IO arguments");
765 if (!mem || size == 0) {
769 ok = (SDL_WriteIO(dst, mem, size) == size);
772 if (closeio && dst) SDL_CloseIO(dst);
788 if (!lr || !lr->core.gameLoaded || !file) {
789 SDL_SetError(
"[SDL_Libretro] Invalid SaveMemory arguments");
796 if (!mem || size == 0)
return true;
798 char tempPath[SDL_LIBRETRO_MAX_PATH];
799 SDL_IOStream* io = SDL_Libretro_OpenSaveFile(file, tempPath,
sizeof(tempPath));
800 if (!io)
return false;
803 SDL_Log(
"[SDL_Libretro] %s saved to %s (%zu bytes)", SDL_Libretro_GetMemoryTypeName(memoryType), file, size);
823 if (!lr || !lr->core.gameLoaded || !src) {
824 SDL_SetError(
"[SDL_Libretro] Invalid LoadMemory_IO arguments");
825 if (closeio && src) SDL_CloseIO(src);
832 SDL_SetError(
"[SDL_Libretro] Core has no %s", SDL_Libretro_GetMemoryTypeName(memoryType));
833 if (closeio) SDL_CloseIO(src);
838 void* data = SDL_LoadFile_IO(src, &fileSize, closeio);
839 if (!data)
return false;
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);
863 if (!lr || !lr->core.gameLoaded || !file) {
864 SDL_SetError(
"[SDL_Libretro] Invalid LoadMemory arguments");
867 SDL_IOStream* io = SDL_IOFromFile(file,
"rb");
868 if (!io)
return false;
871 SDL_Log(
"[SDL_Libretro] %s loaded from %s", SDL_Libretro_GetMemoryTypeName(memoryType), file);
894 if (lr) lr->sramAutoSave = enabled;
906 return lr && lr->sramAutoSave;
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;
917 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Failed to load SRAM: %s", SDL_GetError());
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;
929 SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"[SDL_Libretro] Failed to save SRAM: %s", SDL_GetError());
997#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1012static size_t SDL_Libretro_RewindMaxEncodedSize(
size_t len) {
1013 return len + (len / 128) + 1;
1024static size_t SDL_Libretro_RewindMatchRun(
const unsigned char* a,
const unsigned char* b,
size_t max) {
1026 for (; i +
sizeof(size_t) <= max; i +=
sizeof(size_t)) {
1028 SDL_memcpy(&wa, a + i,
sizeof(
size_t));
1029 SDL_memcpy(&wb, b + i,
sizeof(
size_t));
1030 if (wa != wb)
break;
1032 while (i < max && a[i] == b[i]) i++;
1049static size_t SDL_Libretro_RewindEncodeDelta(
const unsigned char* cur,
const unsigned char* ref,
size_t len,
unsigned char* out,
size_t outCap) {
1051 const size_t kMinSkip = 3;
1053 size_t op = 0, i = 0;
1055 if (cur[i] == ref[i]) {
1056 size_t run = SDL_Libretro_RewindMatchRun(cur + i, ref + i, len - i);
1061 if (op >= outCap)
return 0;
1062 out[op] = (
unsigned char)run;
1067 else if (run < 255) {
1070 if (op >= outCap)
return 0;
1077 size_t chunk = run > 65535 ? 65535 : run;
1079 if (op + 3 > outCap)
return 0;
1081 out[op + 1] = (
unsigned char)(chunk & 0xFF);
1082 out[op + 2] = (
unsigned char)(chunk >> 8);
1091 size_t segStart = i;
1095 while (j < len && cur[j] != ref[j]) 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;
1103 for (
size_t pos = segStart; pos < segEnd;) {
1104 size_t chunk = segEnd - pos;
1105 if (chunk > 128) chunk = 128;
1107 if (op + 1 + chunk > outCap)
return 0;
1108 out[op] = (
unsigned char)(0x80 | (chunk - 1));
1111 for (
size_t k = pos; k < pos + chunk; k++) {
1112 if (out) out[op] = cur[k] ^ ref[k];
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++];
1136 if (dp + 2 > deltaLen)
return false;
1137 size_t skip = delta[dp] | ((size_t)delta[dp + 1] << 8);
1141 else if (tag <= 0x7F) {
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++];
1151 return (sp <= stateLen);
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");
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");
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
1184 encScratch = (
unsigned char*)SDL_malloc(SDL_Libretro_RewindMaxEncodedSize(slotSize));
1185 ok = ok && (encScratch != NULL);
1190 SDL_free(encScratch);
1192 SDL_SetError(
"[SDL_Libretro] Failed to allocate rewind buffers");
1197 lr->rewindReference = ref;
1198 lr->rewindScratch = scratch;
1199 lr->rewindEncodeScratch = encScratch;
1200 lr->rewindEntries = entries;
1201 lr->rewindSlotSize = slotSize;
1202 lr->rewindBytes = 0;
1204 lr->rewindCount = 0;
1205 lr->rewindFrameCounter = 0;
1206 lr->rewindHasReference =
false;
1207 lr->rewindActive =
false;
1229 return SDL_InvalidParamError(
"lr");
1232 SDL_Libretro_RewindFree(lr);
1235 lr->rewindEnabled =
false;
1237 if (lr->core.speed < 0.0f) {
1244 if (bufferFrames == 0) bufferFrames = 300;
1245 if (captureInterval == 0) captureInterval = 1;
1247 lr->rewindEnabled =
true;
1248 lr->rewindCapacity = bufferFrames;
1249 lr->rewindCaptureInterval = captureInterval;
1256 return SDL_Libretro_RewindAllocate(lr);
1266 return lr && lr->rewindEnabled;
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;
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);
1312 lr->rewindMaxBytes = maxBytes;
1313 SDL_Libretro_RewindEvictToBudget(lr);
1323 return lr ? lr->rewindMaxBytes : 0;
1344 return SDL_InvalidParamError(
"lr");
1346 if (!(seconds > 0.0)) {
1347 SDL_SetError(
"[SDL_Libretro] Rewind duration must be positive");
1353 size_t slotSize = lr->rewindSlotSize;
1355 slotSize = lr->core.symbols.retro_serialize_size();
1357 if (slotSize == 0) {
1358 SDL_SetError(
"[SDL_Libretro] Rewind state size unknown; load a serializable core first");
1362 double fps = (lr->core.fps > 0.0) ? lr->core.fps : 60.0;
1363 unsigned interval = (lr->rewindCaptureInterval > 0) ? lr->rewindCaptureInterval : 1;
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++;
1380static void SDL_Libretro_RewindCapture(SDL_Libretro* lr) {
1381 if (!lr->rewindEnabled)
return;
1385 if (!lr->rewindReference) {
1386 if (lr->core.symbols.retro_serialize_size() == 0 || !SDL_Libretro_RewindAllocate(lr))
return;
1389 lr->rewindFrameCounter++;
1390 if (lr->rewindFrameCounter < lr->rewindCaptureInterval)
return;
1391 lr->rewindFrameCounter = 0;
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);
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;
1422 lr->rewindSlotSize = curSize;
1423 SDL_Libretro_ClearRewind(lr);
1426 if (!lr->core.symbols.retro_serialize(lr->rewindScratch, lr->rewindSlotSize))
return;
1428 if (!lr->rewindHasReference) {
1429 SDL_memcpy(lr->rewindReference, lr->rewindScratch, lr->rewindSlotSize);
1430 lr->rewindHasReference =
true;
1437 SDL_LibretroRewindDelta* slot = &lr->rewindEntries[lr->rewindHead];
1438#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1445 size_t storeSize = SDL_Libretro_RewindEncodeDelta(
1447 lr->rewindReference,
1449 lr->rewindEncodeScratch,
1450 SDL_Libretro_RewindMaxEncodedSize(lr->rewindSlotSize));
1451 if (storeSize == 0)
return;
1456 if (slot->capacity < storeSize) {
1457 unsigned char* data = (
unsigned char*)SDL_realloc(slot->data, storeSize);
1459 lr->rewindBytes += storeSize - slot->capacity;
1461 slot->capacity = storeSize;
1463 SDL_memcpy(slot->data, lr->rewindEncodeScratch, storeSize);
1464 slot->length = storeSize;
1467 unsigned char* swap = lr->rewindReference;
1468 lr->rewindReference = lr->rewindScratch;
1469 lr->rewindScratch = swap;
1472 unsigned char* recycled = slot->data;
1473 size_t recycledCap = slot->capacity;
1474 if (recycledCap < lr->rewindSlotSize) {
1476 unsigned char* grown = (
unsigned char*)SDL_realloc(recycled, lr->rewindSlotSize);
1479 recycledCap = lr->rewindSlotSize;
1482 lr->rewindBytes += lr->rewindSlotSize - slot->capacity;
1483 slot->data = lr->rewindReference;
1484 slot->length = lr->rewindSlotSize;
1485 slot->capacity = lr->rewindSlotSize;
1486 lr->rewindReference = lr->rewindScratch;
1487 lr->rewindScratch = recycled;
1490 lr->rewindHead = (lr->rewindHead + 1) % lr->rewindCapacity;
1491 if (lr->rewindCount < lr->rewindCapacity) {
1496 SDL_Libretro_RewindEvictToBudget(lr);
1503static void SDL_Libretro_RewindFreeEntry(SDL_Libretro* lr, SDL_LibretroRewindDelta* entry) {
1504 lr->rewindBytes -= entry->capacity;
1505 SDL_free(entry->data);
1508 entry->capacity = 0;
1517static bool SDL_Libretro_RewindStepState(SDL_Libretro* lr) {
1518 if (!lr->rewindEnabled || !lr->rewindReference || lr->rewindCount == 0)
return false;
1520 lr->rewindHead = (lr->rewindHead == 0) ? (lr->rewindCapacity - 1) : (lr->rewindHead - 1);
1523 SDL_LibretroRewindDelta* entry = &lr->rewindEntries[lr->rewindHead];
1524 if (!entry->data || entry->length == 0)
return false;
1526#ifdef SDL_LIBRETRO_ENABLE_REWIND_DELTA
1527 bool reconstructed = SDL_Libretro_RewindDecodeDelta(entry->data, entry->length, lr->rewindReference, lr->rewindSlotSize);
1530 bool reconstructed = (entry->length == lr->rewindSlotSize);
1531 if (reconstructed) SDL_memcpy(lr->rewindReference, entry->data, lr->rewindSlotSize);
1534 SDL_Libretro_RewindFreeEntry(lr, entry);
1542 if (!reconstructed || !lr->core.symbols.retro_unserialize(lr->rewindReference, lr->rewindSlotSize)) {
1543 SDL_Libretro_ClearRewind(lr);
1558static bool SDL_Libretro_RewindStep(SDL_Libretro* lr) {
1560 SDL_SetError(
"[SDL_Libretro] Rewind is not enabled");
1563 if (!SDL_Libretro_RewindStepState(lr))
return false;
1565 lr->rewindActive =
true;
1566 lr->core.symbols.retro_run();
1567 lr->rewindActive =
false;
1577static void SDL_Libretro_RewindEvictToBudget(SDL_Libretro* lr) {
1578 if (!lr->rewindEnabled || lr->rewindMaxBytes == 0 || !lr->rewindEntries)
return;
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]);
1595static void SDL_Libretro_ClearRewind(SDL_Libretro* lr) {
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;
1605 lr->rewindBytes = 0;
1607 lr->rewindCount = 0;
1608 lr->rewindFrameCounter = 0;
1609 lr->rewindHasReference =
false;
1616static void SDL_Libretro_RewindFree(SDL_Libretro* lr) {
1618 SDL_Libretro_ClearRewind(lr);
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;
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.