From 2d841715cba5225d2fb6f4f6f534591769871fbb Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 19 Jan 2021 11:01:06 -0700 Subject: [PATCH] include achievement runtime state in save states --- cheevos/cheevos.c | 102 ++++++-- cheevos/cheevos.h | 3 + content.h | 9 + deps/rcheevos/include/rcheevos.h | 1 + deps/rcheevos/src/rcheevos/runtime.c | 1 + deps/rcheevos/src/rcheevos/runtime_progress.c | 47 +++- state_manager.c | 30 +-- tasks/task_save.c | 246 ++++++++++++++++-- 8 files changed, 364 insertions(+), 75 deletions(-) diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index c23f0ea7b3..9670c8f3fc 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -144,6 +144,7 @@ typedef struct bool hardcore_active; bool loaded; + bool reset_rewind; bool core_supports; bool leaderboards_enabled; bool leaderboard_notifications; @@ -164,6 +165,7 @@ static rcheevos_locals_t rcheevos_locals = "", /* user_agent_prefix */ false,/* hardcore_active */ false,/* loaded */ + false,/* reset_rewind */ true, /* core_supports */ false,/* leaderboards_enabled */ false,/* leaderboard_notifications */ @@ -1397,6 +1399,8 @@ bool rcheevos_unload(void) rcheevos_locals.hardcore_active = false; } + rcheevos_locals.reset_rewind = false; + rc_runtime_destroy(&rcheevos_locals.runtime); /* if the config-level token has been cleared, we need to re-login on loading the next game */ @@ -1824,18 +1828,31 @@ Test all the achievements (call once per frame). *****************************************************************************/ void rcheevos_test(void) { - settings_t* settings; +#ifdef HAVE_REWIND + if (rcheevos_locals.reset_rewind) + { + const settings_t* settings = config_get_ptr(); + const bool rewind_enable = (settings && settings->bools.rewind_enable); + + rcheevos_locals.reset_rewind = false; + + /* re-enable rewind. if rcheevos_locals.loaded is true, additional space will be allocated + * for the achievement state data */ + if (rewind_enable) + command_event(CMD_EVENT_REWIND_INIT, NULL); + } +#endif if (!rcheevos_locals.loaded) return; - settings = config_get_ptr(); - if (rcheevos_locals.memory.count == 0) { /* we were unable to initialize memory earlier, try now */ if (!rcheevos_memory_init(&rcheevos_locals.memory, rcheevos_locals.patchdata.console_id)) { + const settings_t* settings = config_get_ptr(); + CHEEVOS_ERR(RCHEEVOS_TAG "No memory exposed by core\n"); rcheevos_locals.core_supports = false; @@ -1854,6 +1871,35 @@ void rcheevos_test(void) rc_runtime_do_frame(&rcheevos_locals.runtime, &rcheevos_runtime_event_handler, rcheevos_peek, NULL, 0); } +size_t rcheevos_get_serialize_size(void) +{ + if (!rcheevos_locals.loaded) + return 0; + + return rc_runtime_progress_size(&rcheevos_locals.runtime, NULL); +} + +bool rcheevos_get_serialized_data(void* buffer) +{ + if (!rcheevos_locals.loaded) + return false; + + return (rc_runtime_serialize_progress(buffer, &rcheevos_locals.runtime, NULL) == RC_OK); +} + +bool rcheevos_set_serialized_data(void* buffer) +{ + if (rcheevos_locals.loaded) + { + if (buffer && rc_runtime_deserialize_progress(&rcheevos_locals.runtime, (const unsigned char*)buffer, NULL) == RC_OK) + return true; + + rc_runtime_reset(&rcheevos_locals.runtime); + } + + return false; +} + void rcheevos_set_support_cheevos(bool state) { rcheevos_locals.core_supports = state; @@ -1949,6 +1995,7 @@ enum static int rcheevos_iterate(rcheevos_coro_t* coro) { char buffer[2048]; + bool ret; #ifdef CHEEVOS_TIME_HASH retro_time_t start; #endif @@ -2025,27 +2072,51 @@ static int rcheevos_iterate(rcheevos_coro_t* coro) fclose(file); } #endif - if (rcheevos_parse(&rcheevos_locals, coro->json)) - { - CHEEVOS_FREE(coro->json); - CORO_STOP(); - } +#if HAVE_REWIND + if (!rcheevos_hardcore_active()) + { + /* deactivate rewind while we activate the achievements */ + const bool rewind_enable = coro->settings->bools.rewind_enable; + if (rewind_enable) + command_event(CMD_EVENT_REWIND_DEINIT, NULL); + } +#endif + + ret = rcheevos_parse(&rcheevos_locals, coro->json); CHEEVOS_FREE(coro->json); - if ( rcheevos_locals.patchdata.core_count == 0 - && rcheevos_locals.patchdata.unofficial_count == 0 - && rcheevos_locals.patchdata.lboard_count == 0) + if (ret == 0) { - runloop_msg_queue_push( + if (rcheevos_locals.patchdata.core_count == 0 + && rcheevos_locals.patchdata.unofficial_count == 0 + && rcheevos_locals.patchdata.lboard_count == 0) + { + runloop_msg_queue_push( "This game has no achievements.", 0, 5 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - rcheevos_pause_hardcore(); - CORO_STOP(); + rcheevos_pause_hardcore(); + } + else + { + rcheevos_locals.loaded = true; + } } - rcheevos_locals.loaded = true; +#if HAVE_REWIND + if (!rcheevos_hardcore_active()) + { + /* have to "schedule" this. CMD_EVENT_REWIND_INIT should only be called on the main thread */ + rcheevos_locals.reset_rewind = true; + } +#endif + + if (!rcheevos_locals.loaded) + { + /* parse failure or no achievements - nothing more to do */ + CORO_STOP(); + } /* * Inputs: CHEEVOS_VAR_GAMEID @@ -2699,6 +2770,7 @@ bool rcheevos_load(const void *data) struct rc_hash_cdreader cdreader; rcheevos_locals.loaded = false; + rcheevos_locals.reset_rewind = false; rc_runtime_init(&rcheevos_locals.runtime); if (!cheevos_enable || !rcheevos_locals.core_supports || !data) diff --git a/cheevos/cheevos.h b/cheevos/cheevos.h index ce0d278140..6d79f76d49 100644 --- a/cheevos/cheevos.h +++ b/cheevos/cheevos.h @@ -42,6 +42,9 @@ enum }; bool rcheevos_load(const void *data); +size_t rcheevos_get_serialize_size(void); +bool rcheevos_get_serialized_data(void* buffer); +bool rcheevos_set_serialized_data(void* buffer); void rcheevos_reset_game(void); diff --git a/content.h b/content.h index b00f66f844..ff5929d02a 100644 --- a/content.h +++ b/content.h @@ -51,6 +51,15 @@ bool content_load_state(const char* path, bool load_to_backup_buffer, bool autol /* Save a state from memory to disk. */ bool content_save_state(const char *path, bool save_to_disk, bool autosave); +/* Gets the number of bytes required to serialize the state. */ +size_t content_get_serialized_size(); + +/* Serializes the current state. buffer must be at least content_get_serialized_size bytes */ +bool content_serialize_state(void* buffer, size_t buffer_size); + +/* Deserializes the current state. */ +bool content_deserialize_state(const void* serialized_data, size_t serialized_size); + /* Waits for any in-progress save state tasks to finish */ void content_wait_for_save_state_task(void); diff --git a/deps/rcheevos/include/rcheevos.h b/deps/rcheevos/include/rcheevos.h index 2ff0289750..09923c34a9 100644 --- a/deps/rcheevos/include/rcheevos.h +++ b/deps/rcheevos/include/rcheevos.h @@ -392,6 +392,7 @@ typedef struct rc_runtime_trigger_t { rc_trigger_t* trigger; void* buffer; unsigned char md5[16]; + int serialized_size; char owns_memrefs; } rc_runtime_trigger_t; diff --git a/deps/rcheevos/src/rcheevos/runtime.c b/deps/rcheevos/src/rcheevos/runtime.c index 51094bb43f..48a7646bdc 100644 --- a/deps/rcheevos/src/rcheevos/runtime.c +++ b/deps/rcheevos/src/rcheevos/runtime.c @@ -172,6 +172,7 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, unsigned id, const char* self->triggers[self->trigger_count].id = id; self->triggers[self->trigger_count].trigger = trigger; self->triggers[self->trigger_count].buffer = trigger_buffer; + self->triggers[self->trigger_count].serialized_size = 0; memcpy(self->triggers[self->trigger_count].md5, md5, 16); self->triggers[self->trigger_count].owns_memrefs = owns_memref; ++self->trigger_count; diff --git a/deps/rcheevos/src/rcheevos/runtime_progress.c b/deps/rcheevos/src/rcheevos/runtime_progress.c index a57b7321b3..99984e71f2 100644 --- a/deps/rcheevos/src/rcheevos/runtime_progress.c +++ b/deps/rcheevos/src/rcheevos/runtime_progress.c @@ -110,17 +110,25 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress) rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS); - while (memref) { - flags = memref->memref.size; - if (memref->previous == memref->prior) - flags |= RC_MEMREF_FLAG_PREV_IS_PRIOR; + if (!progress->buffer) { + while (memref) { + progress->offset += 16; + memref = memref->next; + } + } + else { + while (memref) { + flags = memref->memref.size; + if (memref->previous == memref->prior) + flags |= RC_MEMREF_FLAG_PREV_IS_PRIOR; - rc_runtime_progress_write_uint(progress, memref->memref.address); - rc_runtime_progress_write_uint(progress, flags); - rc_runtime_progress_write_uint(progress, memref->value); - rc_runtime_progress_write_uint(progress, memref->prior); + rc_runtime_progress_write_uint(progress, memref->memref.address); + rc_runtime_progress_write_uint(progress, flags); + rc_runtime_progress_write_uint(progress, memref->value); + rc_runtime_progress_write_uint(progress, memref->prior); - memref = memref->next; + memref = memref->next; + } } rc_runtime_progress_end_chunk(progress); @@ -133,6 +141,7 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) unsigned address, flags, value, prior; char size; rc_memref_value_t* memref; + rc_memref_value_t* first_unmatched = progress->runtime->memrefs; /* re-read the chunk size to determine how many memrefs are present */ progress->offset -= 4; @@ -146,12 +155,17 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress) size = flags & 0xFF; - memref = progress->runtime->memrefs; + memref = first_unmatched; while (memref) { if (memref->memref.address == address && memref->memref.size == size) { memref->value = value; memref->previous = (flags & RC_MEMREF_FLAG_PREV_IS_PRIOR) ? prior : value; memref->prior = prior; + + if (memref == first_unmatched) + first_unmatched = memref->next; + + break; } memref = memref->next; @@ -254,6 +268,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress) { unsigned i; + int offset = 0; int result; for (i = 0; i < progress->runtime->trigger_count; ++i) @@ -273,6 +288,15 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres break; } + if (!progress->buffer) { + if(runtime_trigger->serialized_size) { + progress->offset += runtime_trigger->serialized_size; + continue; + } + + offset = progress->offset; + } + rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT); rc_runtime_progress_write_uint(progress, runtime_trigger->id); rc_runtime_progress_write_md5(progress, runtime_trigger->md5); @@ -282,6 +306,9 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres return result; rc_runtime_progress_end_chunk(progress); + + if (!progress->buffer) + runtime_trigger->serialized_size = progress->offset - offset; } return RC_OK; diff --git a/state_manager.c b/state_manager.c index 50fbe48b69..2405dbf94f 100644 --- a/state_manager.c +++ b/state_manager.c @@ -32,6 +32,7 @@ #include "core.h" #include "retroarch.h" #include "verbosity.h" +#include "content.h" #ifdef HAVE_NETWORKING #include "network/netplay/netplay.h" @@ -575,8 +576,6 @@ void state_manager_event_init( struct state_manager_rewind_state *rewind_st, unsigned rewind_buffer_size) { - retro_ctx_serialize_info_t serial_info; - retro_ctx_size_info_t info; void *state = NULL; if (!rewind_st || rewind_st->state) @@ -588,9 +587,7 @@ void state_manager_event_init( return; } - core_serialize_size(&info); - - rewind_st->size = info.size; + rewind_st->size = content_get_serialized_size(); if (!rewind_st->size) { @@ -611,10 +608,7 @@ void state_manager_event_init( state_manager_push_where(rewind_st->state, &state); - serial_info.data = state; - serial_info.size = rewind_st->size; - - core_serialize(&serial_info); + content_serialize_state(state, rewind_st->size); state_manager_push_do(rewind_st->state); } @@ -679,8 +673,6 @@ bool state_manager_check_rewind( if (state_manager_pop(rewind_st->state, &buf)) { - retro_ctx_serialize_info_t serial_info; - #ifdef HAVE_NETWORKING /* Make sure netplay isn't confused */ if (!was_reversed) @@ -696,10 +688,7 @@ bool state_manager_check_rewind( *time = is_paused ? 1 : 30; ret = true; - serial_info.data_const = buf; - serial_info.size = rewind_st->size; - - core_unserialize(&serial_info); + content_deserialize_state(buf, rewind_st->size); #ifdef HAVE_BSV_MOVIE bsv_movie_frame_rewind(); @@ -707,10 +696,7 @@ bool state_manager_check_rewind( } else { - retro_ctx_serialize_info_t serial_info; - serial_info.data_const = buf; - serial_info.size = rewind_st->size; - core_unserialize(&serial_info); + content_deserialize_state(buf, rewind_st->size); #ifdef HAVE_NETWORKING /* Tell netplay we're done */ @@ -741,15 +727,11 @@ bool state_manager_check_rewind( if ((cnt == 0) || rarch_ctl(RARCH_CTL_BSV_MOVIE_IS_INITED, NULL)) { - retro_ctx_serialize_info_t serial_info; void *state = NULL; state_manager_push_where(rewind_st->state, &state); - serial_info.data = state; - serial_info.size = rewind_st->size; - - core_serialize(&serial_info); + content_serialize_state(state, rewind_st->size); state_manager_push_do(rewind_st->state); } diff --git a/tasks/task_save.c b/tasks/task_save.c index 93d99e3fdc..07ee1dff54 100644 --- a/tasks/task_save.c +++ b/tasks/task_save.c @@ -46,6 +46,10 @@ #include "../network/netplay/netplay.h" #endif +#ifdef HAVE_CHEEVOS +#include "../cheevos/cheevos.h" +#endif + #include "../content.h" #include "../core.h" #include "../file_path_special.h" @@ -64,6 +68,11 @@ #define SAVE_STATE_CHUNK 4096 #endif +#define RASTATE_VERSION 1 +#define RASTATE_MEM_BLOCK "MEM " +#define RASTATE_CHEEVOS_BLOCK "ACHV" +#define RASTATE_END_BLOCK "END " + struct ram_type { const char *path; @@ -151,6 +160,15 @@ static struct autosave_st autosave_state; static bool save_state_in_background = false; static struct string_list *task_save_files = NULL; +typedef struct rastate_size_info +{ + size_t total_size; + size_t coremem_size; +#ifdef HAVE_CHEEVOS + size_t cheevos_size; +#endif +} rastate_size_info_t; + #ifdef HAVE_THREADS /** * autosave_thread: @@ -575,13 +593,106 @@ static void task_save_handler_finished(retro_task_t *task, free(state); } -static void *get_serialized_data(const char *path, size_t serial_size) +static size_t content_align_size(size_t size) +{ + /* align to 8-byte boundary */ + return ((size + 7) & ~7); +} + +static bool content_get_rastate_size(rastate_size_info_t* size) +{ + retro_ctx_size_info_t info; + + core_serialize_size(&info); + if (!info.size) + return false; + + size->coremem_size = info.size; + /* 8-byte identifier, 8-byte block header, content, 8-byte terminator */ + size->total_size = 8 + 8 + content_align_size(info.size) + 8; + +#ifdef HAVE_CHEEVOS + size->cheevos_size = rcheevos_get_serialize_size(); + if (size->cheevos_size > 0) + size->total_size += 8 + content_align_size(size->cheevos_size); /* 8-byte block header + content */ +#endif + + return true; +} + +size_t content_get_serialized_size() +{ + rastate_size_info_t size; + if (!content_get_rastate_size(&size)) + return 0; + + return size.total_size; +} + +static void content_write_block_header(unsigned char* output, const char* header, size_t size) +{ + memcpy(output, header, 4); + output[4] = ((size) & 0xFF); + output[5] = ((size >> 8) & 0xFF); + output[6] = ((size >> 16) & 0xFF); + output[7] = ((size >> 24) & 0xFF); +} + +static bool content_write_serialized_state(void* buffer, rastate_size_info_t* size) { retro_ctx_serialize_info_t serial_info; - bool ret = false; - void *data = NULL; + unsigned char* output = (unsigned char*)buffer; - if (!serial_size) + /* 8-byte identifier "RASTATE1" where 1 is the version */ + memcpy(output, "RASTATE", 7); + output[7] = RASTATE_VERSION; + output += 8; + + /* important - write the unaligned size - some cores fail if they aren't passed the exact right size. */ + content_write_block_header(output, RASTATE_MEM_BLOCK, size->coremem_size); + output += 8; + + /* important - pass the unaligned size to the core. some fail if it isn't exactly what they're expecting. */ + serial_info.size = size->coremem_size; + serial_info.data = (void*)output; + if (!core_serialize(&serial_info)) + return false; + + output += content_align_size(size->coremem_size); + +#ifdef HAVE_CHEEVOS + if (size->cheevos_size) + { + content_write_block_header(output, RASTATE_CHEEVOS_BLOCK, size->cheevos_size); + + if (rcheevos_get_serialized_data(output + 8)) + output += content_align_size(size->cheevos_size) + 8; + } +#endif + + content_write_block_header(output, RASTATE_END_BLOCK, 0); + + return true; +} + +bool content_serialize_state(void* buffer, size_t buffer_size) +{ + rastate_size_info_t size; + if (!content_get_rastate_size(&size)) + return false; + + if (size.total_size > buffer_size) + return false; + + return content_write_serialized_state(buffer, &size); +} + +static void *content_get_serialized_data(size_t* serial_size) +{ + void* data; + + rastate_size_info_t size; + if (!content_get_rastate_size(&size)) return NULL; /* Ensure buffer is initialised to zero @@ -589,21 +700,17 @@ static void *get_serialized_data(const char *path, size_t serial_size) * sizes when core requests a larger buffer * than it needs (and leaves the excess * as uninitialised garbage) */ - data = calloc(serial_size, 1); - + data = calloc(size.total_size, 1); if (!data) return NULL; - serial_info.data = data; - serial_info.size = serial_size; - ret = core_serialize(&serial_info); - - if (!ret) + if (!content_write_serialized_state(data, &size)) { free(data); return NULL; } + *serial_size = size.total_size; return data; } @@ -634,7 +741,11 @@ static void task_save_handler(retro_task_t *task) } if (!state->data) - state->data = get_serialized_data(state->path, state->size); + { + size_t size; + state->data = content_get_serialized_data(&size); + state->size = (ssize_t)size; + } remaining = MIN(state->size - state->written, SAVE_STATE_CHUNK); @@ -921,6 +1032,91 @@ error: task_load_handler_finished(task, state); } +static bool content_load_rastate1(unsigned char* input, size_t size) +{ + unsigned char* stop = input + size; + unsigned char* marker; + bool seen_core = false; +#ifdef HAVE_CHEEVOS + bool seen_cheevos = false; +#endif + + input += 8; + while (input < stop) + { + size_t block_size = (input[7] << 24 | input[6] << 16 | input[5] << 8 | input[4]); + marker = input; + input += 8; + + if (memcmp(marker, RASTATE_MEM_BLOCK, 4) == 0) + { + retro_ctx_serialize_info_t serial_info; + serial_info.data_const = (void*)input; + serial_info.size = block_size; + if (!core_unserialize(&serial_info)) + return false; + + seen_core = true; + } +#ifdef HAVE_CHEEVOS + else if (memcmp(marker, RASTATE_CHEEVOS_BLOCK, 4) == 0) + { + if (rcheevos_set_serialized_data((void*)input)) + seen_cheevos = true; + } +#endif + else if (memcmp(marker, RASTATE_END_BLOCK, 4) == 0) + { + break; + } + + input += content_align_size(block_size); + } + + if (!seen_core) + return false; + +#ifdef HAVE_CHEEVOS + if (!seen_cheevos) + rcheevos_set_serialized_data(NULL); +#endif + + return true; +} + +bool content_deserialize_state(const void* serialized_data, size_t serialized_size) +{ + if (memcmp(serialized_data, "RASTATE", 7) != 0) + { + /* old format is just core data, load it directly */ + retro_ctx_serialize_info_t serial_info; + serial_info.data_const = serialized_data; + serial_info.size = serialized_size; + if (!core_unserialize(&serial_info)) + return false; + +#ifdef HAVE_CHEEVOS + rcheevos_set_serialized_data(NULL); +#endif + } + else + { + unsigned char* input = (unsigned char*)serialized_data; + switch (input[7]) /* version */ + { + case 1: + if (!content_load_rastate1(input, serialized_size)) + return false; + break; + + default: + return false; + } + } + + return true; +} + /** * content_load_state_cb: * @path : path that state will be loaded from. @@ -931,7 +1127,6 @@ static void content_load_state_cb(retro_task_t *task, void *task_data, void *user_data, const char *error) { - retro_ctx_serialize_info_t serial_info; unsigned i; bool ret; load_task_data_t *load_data = (load_task_data_t*)task_data; @@ -1024,15 +1219,12 @@ static void content_load_state_cb(retro_task_t *task, } } - serial_info.data_const = buf; - serial_info.size = size; - /* Backup the current state so we can undo this load */ content_save_state("RAM", false, false); - ret = core_unserialize(&serial_info); + ret = content_deserialize_state(buf, size); - /* Flush back. */ + /* Flush back. */ for (i = 0; i < num_blocks; i++) { if (blocks[i].data) @@ -1266,15 +1458,17 @@ bool content_save_state(const char *path, bool save_to_disk, bool autosave) { retro_ctx_size_info_t info; void *data = NULL; + size_t serial_size; core_serialize_size(&info); if (info.size == 0) return false; + serial_size = info.size; if (!save_state_in_background) { - data = get_serialized_data(path, info.size); + data = content_get_serialized_data(&serial_size); if (!data) { @@ -1287,7 +1481,7 @@ bool content_save_state(const char *path, bool save_to_disk, bool autosave) RARCH_LOG("[State]: %s \"%s\", %u %s.\n", msg_hash_to_str(MSG_SAVING_STATE), path, - (unsigned)info.size, + (unsigned)serial_size, msg_hash_to_str(MSG_BYTES)); } @@ -1301,15 +1495,15 @@ bool content_save_state(const char *path, bool save_to_disk, bool autosave) RARCH_LOG("[State]: %s ...\n", msg_hash_to_str(MSG_FILE_ALREADY_EXISTS_SAVING_TO_BACKUP_BUFFER)); - task_push_load_and_save_state(path, data, info.size, true, autosave); + task_push_load_and_save_state(path, data, serial_size, true, autosave); } else - task_push_save_state(path, data, info.size, autosave); + task_push_save_state(path, data, serial_size, autosave); } else { if (!data) - data = get_serialized_data(path, info.size); + data = content_get_serialized_data(&serial_size); if (!data) { @@ -1328,16 +1522,16 @@ bool content_save_state(const char *path, bool save_to_disk, bool autosave) undo_load_buf.data = NULL; } - undo_load_buf.data = malloc(info.size); + undo_load_buf.data = malloc(serial_size); if (!undo_load_buf.data) { free(data); return false; } - memcpy(undo_load_buf.data, data, info.size); + memcpy(undo_load_buf.data, data, serial_size); free(data); - undo_load_buf.size = info.size; + undo_load_buf.size = serial_size; strlcpy(undo_load_buf.path, path, sizeof(undo_load_buf.path)); }