diff --git a/command.c b/command.c index 416db7d15f..4459b7f87c 100644 --- a/command.c +++ b/command.c @@ -1226,7 +1226,7 @@ static void command_event_load_auto_state(void) if (!path_file_exists(savestate_name_auto)) return; - ret = content_load_state(savestate_name_auto); + ret = content_load_state(savestate_name_auto, false); RARCH_LOG("Found auto savestate in: %s\n", savestate_name_auto); @@ -1398,7 +1398,7 @@ static bool command_event_save_auto_state(void) fill_pathname_noext(savestate_name_auto, global->name.savestate, ".auto", sizeof(savestate_name_auto)); - ret = content_save_state((const char*)savestate_name_auto); + ret = content_save_state((const char*)savestate_name_auto, true); RARCH_LOG("Auto save state to \"%s\" %s.\n", savestate_name_auto, ret ? "succeeded" : "failed"); @@ -1568,27 +1568,8 @@ static void command_event_save_state(const char *path, char *s, size_t len) { settings_t *settings = config_get_ptr(); - char buf[PATH_MAX_LENGTH] = {0}; - /* if a save state already exists rename it to .last before saving - * so it can be recovered */ - if (path_file_exists(path)) - { - strlcpy(buf, path, sizeof(buf)); - snprintf(buf, sizeof(buf), "%s", path); - path_remove_extension(buf); - snprintf(buf, sizeof(buf), "%s.last", buf); - - if (!content_rename_state(path, buf)) - { - snprintf(s, len, "%s \"%s\".", - msg_hash_to_str(MSG_FAILED_TO_SAVE_UNDO), - path); - return; - } - } - - if (!content_save_state(path)) + if (!content_save_state(path, true)) { snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_TO_SAVE_STATE_TO), @@ -1604,6 +1585,29 @@ static void command_event_save_state(const char *path, settings->state_slot); } +static void command_event_undo_save_state(char *s, size_t len) +{ + if (content_undo_save_buf_is_empty()) + { + /* TODO/FIXME - use msg_hash_to_str here */ + snprintf(s, len, "%s", + "No save state has been overwritten yet."); + return; + } + + if (!content_undo_save_state()) + { + snprintf(s, len, "%s \"%s\".", + msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE), + "RAM"); + return; + } + + /* TODO/FIXME - use msg_hash_to_str here */ + snprintf(s, len, "%s", + "Restored old save state."); +} + /** * event_load_state * @path : Path to state. @@ -1612,31 +1616,11 @@ static void command_event_save_state(const char *path, * * Loads a state with path being @path. **/ -static void command_event_load_state(const char *path, char *s, size_t len, bool undo) +static void command_event_load_state(const char *path, char *s, size_t len) { settings_t *settings = config_get_ptr(); - char buf[PATH_MAX_LENGTH] = {0}; - /* save a state before loading (unless it's an undo operation already) - * so the state can be recovered - */ - if (!undo) - { - strlcpy(buf, path, sizeof(buf)); - snprintf(buf, sizeof(buf), "%s", path); - path_remove_extension(buf); - snprintf(buf, sizeof(buf), "%s.undo", buf); - - if (!content_save_state(buf)) - { - snprintf(s, len, "%s \"%s\".", - msg_hash_to_str(MSG_FAILED_TO_SAVE_UNDO), - path); - return; - } - } - - if (!content_load_state(path)) + if (!content_load_state(path, false)) { snprintf(s, len, "%s \"%s\".", msg_hash_to_str(MSG_FAILED_TO_LOAD_STATE), @@ -1647,18 +1631,39 @@ static void command_event_load_state(const char *path, char *s, size_t len, bool if (settings->state_slot < 0) snprintf(s, len, "%s #-1 (auto).", msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT)); - else if (!undo) + else snprintf(s, len, "%s #%d.", msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT), settings->state_slot); - else - snprintf(s, len, "%s #-1 (undo).", msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT)); +} + +static void command_event_undo_load_state(char *s, size_t len) +{ + + if (content_undo_load_buf_is_empty()) + { + /* TODO/FIXME - use msg_hash_to_str here */ + snprintf(s, len, "%s", + "No state has been loaded yet."); + return; + } + + if (!content_undo_load_state()) + { + snprintf(s, len, "%s \"%s\".", + msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE), + "RAM"); + return; + } + + /* TODO/FIXME - use msg_hash_to_str here */ + snprintf(s, len, "%s", + "Undid load state."); } static void command_event_main_state(unsigned cmd) { retro_ctx_size_info_t info; char path[PATH_MAX_LENGTH] = {0}; - char buf[PATH_MAX_LENGTH] = {0}; char msg[128] = {0}; global_t *global = global_get_ptr(); settings_t *settings = config_get_ptr(); @@ -1682,33 +1687,13 @@ static void command_event_main_state(unsigned cmd) command_event_save_state(path, msg, sizeof(msg)); break; case CMD_EVENT_LOAD_STATE: - command_event_load_state(path, msg, sizeof(msg), false); + command_event_load_state(path, msg, sizeof(msg)); break; case CMD_EVENT_UNDO_LOAD_STATE: - strlcpy(buf, path, sizeof(buf)); - path_remove_extension(buf); - snprintf(buf, sizeof(buf), "%s.undo", buf); - - if (path_file_exists(buf)) - command_event_load_state(buf, msg, sizeof(msg), true); - else - { - snprintf(msg, sizeof(msg), "%s.", - msg_hash_to_str(MSG_FAILED_TO_LOAD_UNDO)); - } + command_event_undo_load_state(msg, sizeof(msg)); break; case CMD_EVENT_UNDO_SAVE_STATE: - strlcpy(buf, path, sizeof(buf)); - path_remove_extension(buf); - snprintf(buf, sizeof(buf), "%s.last", buf); - - if (path_file_exists(buf)) - command_event_load_state(buf, msg, sizeof(msg), true); - else - { - snprintf(msg, sizeof(msg), "%s.", - msg_hash_to_str(MSG_FAILED_TO_LOAD_UNDO)); - } + command_event_undo_save_state(msg, sizeof(msg)); break; } } @@ -2103,6 +2088,7 @@ bool command_event(enum event_command cmd, void *data) break; case CMD_EVENT_CORE_DEINIT: { + content_reset_savestate_backups(); struct retro_hw_render_callback *hwr = video_driver_get_hw_context(); command_event_deinit_core(true); @@ -2113,6 +2099,7 @@ bool command_event(enum event_command cmd, void *data) break; } case CMD_EVENT_CORE_INIT: + content_reset_savestate_backups(); if (!command_event_init_core((enum rarch_core_type*)data)) return false; break; diff --git a/command.h b/command.h index 205728fde4..2725022923 100644 --- a/command.h +++ b/command.h @@ -48,7 +48,9 @@ enum event_command CMD_EVENT_LOAD_CORE_PERSIST, CMD_EVENT_UNLOAD_CORE, CMD_EVENT_LOAD_STATE, + /* Swaps the current state with what's on the undo load buffer */ CMD_EVENT_UNDO_LOAD_STATE, + /* Rewrites a savestate on disk */ CMD_EVENT_UNDO_SAVE_STATE, CMD_EVENT_SAVE_STATE, CMD_EVENT_SAVE_STATE_DECREMENT, diff --git a/content.h b/content.h index 432fbbd36e..c50773aaed 100644 --- a/content.h +++ b/content.h @@ -46,16 +46,19 @@ bool content_load_ram_file(unsigned slot); bool content_save_ram_file(unsigned slot); /* Load a state from disk to memory. */ -bool content_load_state(const char *path); +bool content_load_state(const char* path, bool load_to_backup_buffer); /* Save a state from memory to disk. */ -bool content_save_state(const char *path); +bool content_save_state(const char *path, bool save_to_disk); /* Copy a save state. */ bool content_rename_state(const char *origin, const char *dest); -/* Load a state backup from disk to memory. */ -bool content_undo_load_state(const char *path); +/* Undoes the last load state operation that was done */ +bool content_undo_load_state(); + +/* Restores the last savestate file which was overwritten */ +bool content_undo_save_state(); bool content_does_not_need_content(void); @@ -73,6 +76,13 @@ void content_deinit(void); * selected libretro core. */ bool content_init(void); +/* Resets the state and savefile backup buffers */ +bool content_reset_savestate_backups(); + +/* Checks if the buffers are empty */ +bool content_undo_load_buf_is_empty(); +bool content_undo_save_buf_is_empty(); + RETRO_END_DECLS #endif diff --git a/internal buffer b/internal buffer new file mode 100644 index 0000000000..84b907ed8e Binary files /dev/null and b/internal buffer differ diff --git a/intl/msg_hash_fr.c b/intl/msg_hash_fr.c index d89bdbf412..30ca14232a 100644 --- a/intl/msg_hash_fr.c +++ b/intl/msg_hash_fr.c @@ -148,9 +148,9 @@ const char *msg_hash_to_str_fr(enum msg_hash_enums msg) return "Impossible de charger la savestate à partir de"; case MSG_FAILED_TO_SAVE_STATE_TO: return "Impossible de sauvegarder la savestate vers"; - case MSG_FAILED_TO_LOAD_UNDO: + case MSG_FAILED_TO_UNDO_LOAD_STATE: return "Aucun savestate de retour arrière trouvé"; - case MSG_FAILED_TO_SAVE_UNDO: + case MSG_FAILED_TO_UNDO_SAVE_STATE: return "Impossible de sauvegarder les informations de savestate de retour arrière"; case MSG_FAILED_TO_SAVE_SRAM: return "Impossible de sauvegarder la SRAM"; diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 71affb7cfb..ce2394fec9 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -2120,13 +2120,13 @@ const char *msg_hash_to_str_us(enum msg_hash_enums msg) case MSG_RESET: return "Reset"; case MSG_FAILED_TO_LOAD_STATE: - return "Failed to load state from"; + return "Nothing to undo."; case MSG_FAILED_TO_SAVE_STATE_TO: return "Failed to save state to"; - case MSG_FAILED_TO_LOAD_UNDO: - return "No undo state found"; - case MSG_FAILED_TO_SAVE_UNDO: - return "Failed to save undo information"; + case MSG_FAILED_TO_UNDO_LOAD_STATE: + return "Failed to undo load state."; + case MSG_FAILED_TO_UNDO_SAVE_STATE: + return "Failed to undo save state."; case MSG_FAILED_TO_SAVE_SRAM: return "Failed to save SRAM"; case MSG_STATE_SIZE: diff --git a/msg_hash.h b/msg_hash.h index afb66efe87..6eaa8481ae 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -198,8 +198,8 @@ enum msg_hash_enums MSG_SAVED_STATE_TO_SLOT, MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES, MSG_FAILED_TO_LOAD_STATE, - MSG_FAILED_TO_LOAD_UNDO, - MSG_FAILED_TO_SAVE_UNDO, + MSG_FAILED_TO_UNDO_LOAD_STATE, + MSG_FAILED_TO_UNDO_SAVE_STATE, MSG_RESET, MSG_AUDIO_MUTED, MSG_AUDIO_UNMUTED, diff --git a/tasks/task_save_state.c b/tasks/task_save_state.c index b94fddadb7..b95bbefcbb 100644 --- a/tasks/task_save_state.c +++ b/tasks/task_save_state.c @@ -37,6 +37,21 @@ #include "../verbosity.h" #include "tasks_internal.h" +struct save_state_buf +{ + void* data; + char path[PATH_MAX_LENGTH]; + size_t size; +}; + +/* Holds the previous saved state + * Can be restored to disk with undo_save_state(). */ +static struct save_state_buf undo_save_buf; + +/* Holds the data from before a load_state() operation + * Can be restored with undo_load_state(). */ +static struct save_state_buf undo_load_buf; + struct sram_block { unsigned type; @@ -44,17 +59,172 @@ struct sram_block size_t size; }; +/** + * undo_load_state: + * Revert to the state before a state was loaded. + * + * Returns: true if successful, false otherwise. + **/ +bool content_undo_load_state() +{ + unsigned i; + //ssize_t size; + retro_ctx_serialize_info_t serial_info; + unsigned num_blocks = 0; + //void *buf = NULL; + struct sram_block *blocks = NULL; + settings_t *settings = config_get_ptr(); + global_t *global = global_get_ptr(); + //bool ret = filestream_read_file(path, &buf, &size); + + RARCH_LOG("%s: \"%s\".\n", + msg_hash_to_str(MSG_LOADING_STATE), + undo_load_buf.path); + + RARCH_LOG("%s: %u %s.\n", + msg_hash_to_str(MSG_STATE_SIZE), + undo_load_buf.size, + msg_hash_to_str(MSG_BYTES)); + + + /* TODO/FIXME - This checking of SRAM overwrite, the backing up of it and + its flushing could all be in their own functions... */ + if (settings->block_sram_overwrite && global->savefiles + && global->savefiles->size) + { + RARCH_LOG("%s.\n", + msg_hash_to_str(MSG_BLOCKING_SRAM_OVERWRITE)); + blocks = (struct sram_block*) + calloc(global->savefiles->size, sizeof(*blocks)); + + if (blocks) + { + num_blocks = global->savefiles->size; + for (i = 0; i < num_blocks; i++) + blocks[i].type = global->savefiles->elems[i].attr.i; + } + } + + for (i = 0; i < num_blocks; i++) + { + retro_ctx_memory_info_t mem_info; + + mem_info.id = blocks[i].type; + core_get_memory(&mem_info); + + blocks[i].size = mem_info.size; + } + + for (i = 0; i < num_blocks; i++) + if (blocks[i].size) + blocks[i].data = malloc(blocks[i].size); + + /* Backup current SRAM which is overwritten by unserialize. */ + for (i = 0; i < num_blocks; i++) + { + if (blocks[i].data) + { + retro_ctx_memory_info_t mem_info; + const void *ptr = NULL; + + mem_info.id = blocks[i].type; + + core_get_memory(&mem_info); + + ptr = mem_info.data; + if (ptr) + memcpy(blocks[i].data, ptr, blocks[i].size); + } + } + + /* We need to make a temporary copy of the buffer, to allow the swap below */ + void* temp_data = malloc(undo_load_buf.size); + size_t temp_data_size = undo_load_buf.size; + memcpy(temp_data, undo_load_buf.data, undo_load_buf.size); + + serial_info.data_const = temp_data; + serial_info.size = temp_data_size; + + /* Swap the current state with the backup state. This way, we can undo + what we're undoing */ + content_save_state("RAM", false); + bool ret = core_unserialize(&serial_info); + + /* Clean up the temporary copy */ + free(temp_data); + temp_data = NULL; + temp_data_size = 0; + + /* Flush back. */ + for (i = 0; i < num_blocks; i++) + { + if (blocks[i].data) + { + retro_ctx_memory_info_t mem_info; + void *ptr = NULL; + + mem_info.id = blocks[i].type; + + core_get_memory(&mem_info); + + ptr = mem_info.data; + if (ptr) + memcpy(ptr, blocks[i].data, blocks[i].size); + } + } + + for (i = 0; i < num_blocks; i++) + free(blocks[i].data); + free(blocks); + + if (!ret) { + RARCH_ERR("%s \"%s\".\n", + msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE), + undo_load_buf.path); + } + + return ret; +} + +/** + * undo_save_state: + * Reverts the last save operation + * + * Returns: true if successful, false otherwise. + **/ +bool content_undo_save_state() +{ + bool ret = filestream_write_file(undo_save_buf.path, undo_save_buf.data, undo_save_buf.size); + + /* Wipe the save file buffer as it's intended to be one use only */ + undo_save_buf.path[0] = '\0'; + undo_save_buf.size = 0; + if (undo_save_buf.data) { + free(undo_save_buf.data); + undo_save_buf.data = NULL; + } + + if (!ret) { + RARCH_ERR("%s \"%s\".\n", + msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE), + undo_save_buf.path); + } + + return ret; +} + + /* TODO/FIXME - turn this into actual task */ /** * save_state: * @path : path of saved state that shall be written to. - * + * @save_to_disk: If false, saves the state onto undo_load_buf. * Save a state from memory to disk. * * Returns: true if successful, false otherwise. **/ -bool content_save_state(const char *path) +bool content_save_state(const char *path, bool save_to_disk) { retro_ctx_serialize_info_t serial_info; retro_ctx_size_info_t info; @@ -84,8 +254,43 @@ bool content_save_state(const char *path) serial_info.size = info.size; ret = core_serialize(&serial_info); - if (ret) - ret = filestream_write_file(path, data, info.size); + if (ret) { + if (save_to_disk) { + if (path_file_exists(path)) { + /* Before overwritting the savestate file, load it into a buffer + to allow undo_save_state() to work */ + /* TODO/FIXME - Use msg_hash_to_str here */ + RARCH_LOG("%s\n", + "File already exists. Saving to backup buffer..."); + + content_load_state(path, true); + } + + ret = filestream_write_file(path, data, info.size); + } + + else + { + /* save_to_disk is false, which means we are saving the state + in undo_load_buf to allow content_undo_load_state() to restore it */ + + /* If we were holding onto an old state already, clean it up first */ + if (undo_load_buf.data) { + free(undo_load_buf.data); + undo_load_buf.data = NULL; + } + + undo_load_buf.data = malloc(info.size); + if (!undo_load_buf.data) { + free(data); + return false; + } + + memcpy(undo_load_buf.data, data, info.size); + undo_load_buf.size = info.size; + strcpy(undo_load_buf.path, path); + } + } else { RARCH_ERR("%s \"%s\".\n", @@ -101,12 +306,14 @@ bool content_save_state(const char *path) /** * content_load_state: * @path : path that state will be loaded from. - * + * @load_to_backup_buffer: If true, the state will be loaded into undo_save_buf. * Load a state from disk to memory. * * Returns: true if successful, false otherwise. + * + * **/ -bool content_load_state(const char *path) +bool content_load_state(const char *path, bool load_to_backup_buffer) { unsigned i; ssize_t size; @@ -130,6 +337,28 @@ bool content_load_state(const char *path) (unsigned)size, msg_hash_to_str(MSG_BYTES)); + /* This means we're backing up the file in memory, so content_undo_save_state() + can restore it */ + if (load_to_backup_buffer) { + + /* If we were previously backing up a file, let go of it first */ + if (undo_save_buf.data) { + free(undo_save_buf.data); + undo_save_buf.data = NULL; + } + + undo_save_buf.data = malloc(size); + if (!undo_save_buf.data) + goto error; + + memcpy(undo_save_buf.data, buf, size); + undo_save_buf.size = size; + strcpy(undo_save_buf.path, path); + + free(buf); + return true; + } + if (settings->block_sram_overwrite && global->savefiles && global->savefiles->size) { @@ -181,6 +410,9 @@ bool content_load_state(const char *path) serial_info.data_const = buf; serial_info.size = size; + + /* Backup the current state so we can undo this load */ + content_save_state("RAM", false); ret = core_unserialize(&serial_info); /* Flush back. */ @@ -233,3 +465,44 @@ bool content_rename_state(const char *origin, const char *dest) RARCH_LOG("Error %d renaming file %s\n", ret, origin); return false; } + +/* +* +* TODO/FIXME: Figure out when and where this should be called. +* As it is, when e.g. closing Gambatte, we get the same printf message 4 times. +* +*/ +bool content_reset_savestate_backups() +{ + printf("Resetting undo buffers.\n"); + + if (undo_save_buf.data) + { + free(undo_save_buf.data); + undo_save_buf.data = NULL; + } + + undo_save_buf.path[0] = '\0'; + undo_save_buf.size = 0; + + if (undo_load_buf.data) + { + free(undo_load_buf.data); + undo_load_buf.data = NULL; + } + + undo_load_buf.path[0] = '\0'; + undo_load_buf.size = 0; + + return true; +} + +bool content_undo_load_buf_is_empty() +{ + return undo_load_buf.data == NULL || undo_load_buf.size == 0; +} + +bool content_undo_save_buf_is_empty() +{ + return undo_save_buf.data == NULL || undo_save_buf.size == 0; +}