diff --git a/command.c b/command.c index 74449fc5d1..d6c5440ef9 100644 --- a/command.c +++ b/command.c @@ -1578,25 +1578,6 @@ 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)) { @@ -1614,6 +1595,22 @@ 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_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 and there */ + snprintf(s, len, "%s", + "Restored save state."); +} + /** * event_load_state * @path : Path to state. @@ -1622,29 +1619,9 @@ 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)) { @@ -1657,18 +1634,29 @@ 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_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 and there */ + 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(); @@ -1692,33 +1680,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; } } @@ -1821,6 +1789,7 @@ bool command_event(enum event_command cmd, void *data) #ifndef HAVE_DYNAMIC command_event(CMD_EVENT_QUIT, NULL); #endif + content_reset_savestate_backups(); break; case CMD_EVENT_LOAD_STATE: /* Immutable - disallow savestate load when @@ -2120,9 +2089,12 @@ bool command_event(enum event_command cmd, void *data) if (hwr) memset(hwr, 0, sizeof(*hwr)); + content_reset_savestate_backups(); + 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..fce1ac2392 100644 --- a/command.h +++ b/command.h @@ -49,6 +49,7 @@ enum event_command CMD_EVENT_UNLOAD_CORE, CMD_EVENT_LOAD_STATE, 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..00e2796c29 100644 --- a/content.h +++ b/content.h @@ -46,16 +46,21 @@ 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 content_load_state_with_backup(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_with_backup(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 +78,9 @@ void content_deinit(void); * selected libretro core. */ bool content_init(void); +/* Resets the state and savefile backup buffers */ +bool content_reset_savestate_backups(); + 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_us.c b/intl/msg_hash_us.c index 5c331af928..201d20f599 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -112,13 +112,13 @@ const char *msg_hash_to_str_us(uint32_t hash) 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 03a11f6b4d..d9481d08ac 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -125,8 +125,8 @@ #define MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES 0xd50adf46U #define MSG_FAILED_TO_LOAD_STATE 0x91f348ebU -#define MSG_FAILED_TO_LOAD_UNDO 0xb6e2fc55U -#define MSG_FAILED_TO_SAVE_UNDO 0xf2e29478U +#define MSG_FAILED_TO_UNDO_LOAD_STATE 0xb6e2fc55U +#define MSG_FAILED_TO_UNDO_SAVE_STATE 0xf2e29478U #define MSG_RESET 0x10474288U diff --git a/tasks/task_save_state.c b/tasks/task_save_state.c index 8c8a8dd002..b605988834 100644 --- a/tasks/task_save_state.c +++ b/tasks/task_save_state.c @@ -35,6 +35,26 @@ #include "../verbosity.h" #include "tasks_internal.h" +struct save_state_buf +{ + void* data; + char path[PATH_MAX_LENGTH]; + size_t size; +}; + +/* +Holds a savestate which was stored on disk and was lost when +content_save_state() wrote over it. +Can be restored to disk with undo_save_state(). +*/ +static struct save_state_buf old_save_file; + +/* +Represents the state which was lost when load_state() was called. +Can be restored with undo_load_state(). +*/ +static struct save_state_buf old_state_buf; + struct sram_block { unsigned type; @@ -42,17 +62,163 @@ struct sram_block size_t size; }; +bool content_undo_load_state() +{ + if (old_state_buf.data == NULL || old_state_buf.size == 0) + return false; + + 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), + "RAM"); + + RARCH_LOG("%s: %u %s.\n", + msg_hash_to_str(MSG_STATE_SIZE), + old_state_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(old_state_buf.size); + size_t temp_data_size = old_state_buf.size; + memcpy(temp_data, old_state_buf.data, old_state_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_with_backup("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), + "RAM"); + + return ret; +} + +bool content_undo_save_state() +{ + bool ret = filestream_write_file(old_save_file.path, old_save_file.data, old_save_file.size); + + /* Wipe the save file buffer as it's intended to be one use only */ + old_save_file.path[0] = '\0'; + if (old_save_file.data) { + free(old_save_file.data); + old_save_file.data = NULL; + } + + old_save_file.data = 0; + + if (!ret) + RARCH_ERR("%s \"%s\".\n", + msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE), + "RAM"); + + 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 old_state_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) { content_save_state_with_backup(path, true);} +bool content_save_state_with_backup(const char *path, bool save_to_disk) { retro_ctx_serialize_info_t serial_info; retro_ctx_size_info_t info; @@ -82,8 +248,31 @@ 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)) { + content_load_state_with_backup(path, true); + } + + ret = filestream_write_file(path, data, info.size); + } + /* save_to_disk is false, which means we are saving the state + in old_state_buf to allow content_undo_load_state() to restore it */ + else + { + old_state_buf.path[0] = '\0'; + + /* If we were holding onto an old state already, clean it up first */ + if (old_state_buf.data) { + free(old_state_buf.data); + old_state_buf.data = NULL; + } + + old_state_buf.data = malloc(info.size); + memcpy(old_state_buf.data, data, info.size); + old_state_buf.size = info.size; + } + } else { RARCH_ERR("%s \"%s\".\n", @@ -99,12 +288,15 @@ 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 old_save_file. * 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) { content_load_state_with_backup(path, false); } +bool content_load_state_with_backup(const char *path, bool load_to_backup_buffer) { unsigned i; ssize_t size; @@ -128,6 +320,26 @@ 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) { + strcpy(old_save_file.path, path); + + /* If we were previously backing up a file, let go of it first */ + if (old_save_file.data) { + free(old_save_file.data); + old_save_file.data = NULL; + } + + old_save_file.data = malloc(size); + memcpy(old_save_file.data, buf, size); + + old_save_file.size = size; + + free(buf); + return true; + } + if (settings->block_sram_overwrite && global->savefiles && global->savefiles->size) { @@ -179,6 +391,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_with_backup("RAM", false); ret = core_unserialize(&serial_info); /* Flush back. */ @@ -231,3 +446,33 @@ bool content_rename_state(const char *origin, const char *dest) RARCH_LOG ("Error %d renaming file %s", ret, origin); return false; } + +/* +* +* TODO/FIXME: Figure out when and where this should be called +* +*/ +bool content_reset_savestate_backups() +{ + printf("Resetting undo buffers.\n"); + + if (old_save_file.data) + { + free(old_save_file.data); + old_save_file.data = NULL; + } + + old_save_file.path[0] = '\0'; + old_save_file.size = 0; + + if (old_state_buf.data) + { + free(old_state_buf.data); + old_state_buf.data = NULL; + } + + old_state_buf.path[0] = '\0'; + old_state_buf.size = 0; + + return true; +}