From 0d1fd657abb19e542ffdbd0aed16ed36aa57e429 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Mon, 20 Apr 2020 12:47:13 +0100 Subject: [PATCH] Add optional save (SRAM) file compression --- config.def.h | 4 ++ configuration.c | 1 + configuration.h | 1 + content.h | 2 +- intl/msg_hash_lbl.h | 2 + intl/msg_hash_us.h | 8 ++++ menu/cbs/menu_cbs_sublabel.c | 4 ++ menu/menu_displaylist.c | 1 + menu/menu_setting.c | 15 +++++++ msg_hash.h | 1 + tasks/task_save.c | 76 ++++++++++++++++++++++++++++++------ 11 files changed, 103 insertions(+), 12 deletions(-) diff --git a/config.def.h b/config.def.h index 24c8e8866c..332fc5a716 100644 --- a/config.def.h +++ b/config.def.h @@ -911,6 +911,10 @@ static const bool savestate_auto_load = false; static const bool savestate_thumbnail_enable = false; +/* When creating save (srm) files, compress + * written data */ +#define DEFAULT_SAVE_FILE_COMPRESSION false + /* When creating save state files, compress * written data */ #define DEFAULT_SAVESTATE_FILE_COMPRESSION false diff --git a/configuration.c b/configuration.c index 78b08b9d9e..02c3cd8441 100644 --- a/configuration.c +++ b/configuration.c @@ -1614,6 +1614,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings, SETTING_BOOL("savestate_auto_save", &settings->bools.savestate_auto_save, true, savestate_auto_save, false); SETTING_BOOL("savestate_auto_load", &settings->bools.savestate_auto_load, true, savestate_auto_load, false); SETTING_BOOL("savestate_thumbnail_enable", &settings->bools.savestate_thumbnail_enable, true, savestate_thumbnail_enable, false); + SETTING_BOOL("save_file_compression", &settings->bools.save_file_compression, true, DEFAULT_SAVE_FILE_COMPRESSION, false); SETTING_BOOL("savestate_file_compression", &settings->bools.savestate_file_compression, true, DEFAULT_SAVESTATE_FILE_COMPRESSION, false); SETTING_BOOL("history_list_enable", &settings->bools.history_list_enable, true, DEFAULT_HISTORY_LIST_ENABLE, false); SETTING_BOOL("playlist_entry_rename", &settings->bools.playlist_entry_rename, true, DEFAULT_PLAYLIST_ENTRY_RENAME, false); diff --git a/configuration.h b/configuration.h index 2f69dfd785..13be111aa9 100644 --- a/configuration.h +++ b/configuration.h @@ -344,6 +344,7 @@ typedef struct settings bool savestate_auto_save; bool savestate_auto_load; bool savestate_thumbnail_enable; + bool save_file_compression; bool savestate_file_compression; bool network_cmd_enable; bool stdin_cmd_enable; diff --git a/content.h b/content.h index fb44c05e21..c8170d17b8 100644 --- a/content.h +++ b/content.h @@ -43,7 +43,7 @@ typedef struct content_ctx_info bool content_load_ram_file(unsigned slot); /* Save a RAM state from memory to disk. */ -bool content_save_ram_file(unsigned slot); +bool content_save_ram_file(unsigned slot, bool compress); /* Load a state from disk to memory. */ bool content_load_state(const char* path, bool load_to_backup_buffer, bool autoload); diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index adf86be403..f23664bb7e 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1200,6 +1200,8 @@ MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD, "savestate_auto_load") MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE, "savestate_thumbnails") +MSG_HASH(MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION, + "save_file_compression") MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION, "savestate_file_compression") MSG_HASH(MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 7852368314..b2a10648a9 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2616,6 +2616,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_SAVESTATE_THUMBNAIL_ENABLE, "Show thumbnails of save states inside the menu." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SAVE_FILE_COMPRESSION, + "SaveRAM Compression" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_SAVE_FILE_COMPRESSION, + "Write non-volatile Save RAM files in an archived format. Dramatically reduces file size at the expense of (negligibly) increased saving/loading times. Note: Only applies to cores that enable saving via the standard libretro Save RAM interface." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_SAVESTATE_FILE_COMPRESSION, "Savestate Compression" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index f798140732..c26f18148b 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -381,6 +381,7 @@ default_sublabel_macro(action_bind_sublabel_perfcnt_enable, MENU_ default_sublabel_macro(action_bind_sublabel_savestate_auto_save, MENU_ENUM_SUBLABEL_SAVESTATE_AUTO_SAVE) default_sublabel_macro(action_bind_sublabel_savestate_auto_load, MENU_ENUM_SUBLABEL_SAVESTATE_AUTO_LOAD) default_sublabel_macro(action_bind_sublabel_savestate_thumbnail_enable, MENU_ENUM_SUBLABEL_SAVESTATE_THUMBNAIL_ENABLE) +default_sublabel_macro(action_bind_sublabel_save_file_compression, MENU_ENUM_SUBLABEL_SAVE_FILE_COMPRESSION) default_sublabel_macro(action_bind_sublabel_savestate_file_compression, MENU_ENUM_SUBLABEL_SAVESTATE_FILE_COMPRESSION) default_sublabel_macro(action_bind_sublabel_autosave_interval, MENU_ENUM_SUBLABEL_AUTOSAVE_INTERVAL) default_sublabel_macro(action_bind_sublabel_input_remap_binds_enable, MENU_ENUM_SUBLABEL_INPUT_REMAP_BINDS_ENABLE) @@ -2328,6 +2329,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_thumbnail_enable); break; + case MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_save_file_compression); + break; case MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_file_compression); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 2ed033e330..e58440cacc 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -6855,6 +6855,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE, PARSE_ONLY_BOOL}, + {MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 5496e1b79c..bcf8d6b501 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -8951,6 +8951,21 @@ static bool setting_append_list( SD_FLAG_NONE); #if defined(HAVE_ZLIB) + CONFIG_BOOL( + list, list_info, + &settings->bools.save_file_compression, + MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION, + MENU_ENUM_LABEL_VALUE_SAVE_FILE_COMPRESSION, + DEFAULT_SAVE_FILE_COMPRESSION, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + CONFIG_BOOL( list, list_info, &settings->bools.savestate_file_compression, diff --git a/msg_hash.h b/msg_hash.h index 061a01134d..091809e036 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1607,6 +1607,7 @@ enum msg_hash_enums MENU_LABEL(SAVESTATE_AUTO_SAVE), MENU_LABEL(SAVESTATE_AUTO_LOAD), MENU_LABEL(SAVESTATE_THUMBNAIL_ENABLE), + MENU_LABEL(SAVE_FILE_COMPRESSION), MENU_LABEL(SAVESTATE_FILE_COMPRESSION), MENU_LABEL(SUSPEND_SCREENSAVER_ENABLE), diff --git a/tasks/task_save.c b/tasks/task_save.c index d11c133ca7..2cdbc37e9f 100644 --- a/tasks/task_save.c +++ b/tasks/task_save.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,7 @@ struct autosave volatile bool quit; size_t bufsize; unsigned interval; + bool compress_files; void *buffer; const void *retro_buffer; const char *path; @@ -163,15 +165,22 @@ static void autosave_thread(void *data) if (differ) { + intfstream_t *file = NULL; + /* Should probably deal with this more elegantly. */ - RFILE *file = filestream_open(save->path, - RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (save->compress_files) + file = intfstream_open_rzip_file(save->path, + RETRO_VFS_FILE_ACCESS_WRITE); + else + file = intfstream_open_file(save->path, + RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (file) { - filestream_write(file, save->buffer, save->bufsize); - filestream_flush(file); - filestream_close(file); + intfstream_write(file, save->buffer, save->bufsize); + intfstream_flush(file); + intfstream_close(file); + free(file); } } @@ -206,7 +215,7 @@ static void autosave_thread(void *data) **/ static autosave_t *autosave_new(const char *path, const void *data, size_t size, - unsigned interval) + unsigned interval, bool compress) { void *buf = NULL; autosave_t *handle = (autosave_t*)malloc(sizeof(*handle)); @@ -216,6 +225,7 @@ static autosave_t *autosave_new(const char *path, handle->quit = false; handle->bufsize = size; handle->interval = interval; + handle->compress_files = compress; handle->retro_buffer = data; handle->path = path; @@ -268,6 +278,11 @@ bool autosave_init(void) autosave_t **list = NULL; settings_t *settings = config_get_ptr(); unsigned autosave_interval = settings->uints.autosave_interval; +#if defined(HAVE_ZLIB) + bool compress_files = settings->bools.save_file_compression; +#else + bool compress_files = false; +#endif if (autosave_interval < 1 || !task_save_files) return false; @@ -299,7 +314,8 @@ bool autosave_init(void) auto_st = autosave_new(path, mem_info.data, mem_info.size, - autosave_interval); + autosave_interval, + compress_files); if (!auto_st) { @@ -1471,7 +1487,22 @@ bool content_load_ram_file(unsigned slot) if (!content_get_memory(&mem_info, &ram, slot)) return false; + /* On first run of content, SRAM file will + * not exist. This is a common enough occurrence + * that we should check before attempting to + * invoke the relevant read_file() function */ + if (string_is_empty(ram.path) || + !path_is_valid(ram.path)) + return false; + +#if defined(HAVE_ZLIB) + /* Always use RZIP interface when reading SRAM + * files - this will automatically handle uncompressed + * data */ + if (!rzipstream_read_file(ram.path, &buf, &rc)) +#else if (!filestream_read_file(ram.path, &buf, &rc)) +#endif return false; if (rc > 0) @@ -1539,6 +1570,15 @@ static bool dump_to_file_desperate(const void *data, free(application_data); free(timebuf); + /* Fallback (emergency) saves are always + * uncompressed + * > If a regular save fails, then the host + * system is experiencing serious technical + * difficulties (most likely some kind of + * hardware failure) + * > In this case, we don't want to further + * complicate matters by introducing zlib + * compression overheads */ if (!filestream_write_file(path, data, size)) { free(path); @@ -1558,10 +1598,11 @@ static bool dump_to_file_desperate(const void *data, * Save a RAM state from memory to disk. * */ -bool content_save_ram_file(unsigned slot) +bool content_save_ram_file(unsigned slot, bool compress) { struct ram_type ram; retro_ctx_memory_info_t mem_info; + bool write_success; if (!content_get_memory(&mem_info, &ram, slot)) return false; @@ -1572,8 +1613,16 @@ bool content_save_ram_file(unsigned slot) msg_hash_to_str(MSG_TO), ram.path); - if (!filestream_write_file( - ram.path, mem_info.data, mem_info.size)) +#if defined(HAVE_ZLIB) + if (compress) + write_success = rzipstream_write_file( + ram.path, mem_info.data, mem_info.size); + else +#endif + write_success = filestream_write_file( + ram.path, mem_info.data, mem_info.size); + + if (!write_success) { RARCH_ERR("%s.\n", msg_hash_to_str(MSG_FAILED_TO_SAVE_SRAM)); @@ -1602,6 +1651,11 @@ bool event_save_files(bool is_sram_used) unsigned i; settings_t *settings = config_get_ptr(); const char *path_cheat_database = settings->paths.path_cheat_database; +#if defined(HAVE_ZLIB) + bool compress_files = settings->bools.save_file_compression; +#else + bool compress_files = false; +#endif cheat_manager_save_game_specific_cheats( path_cheat_database); @@ -1609,7 +1663,7 @@ bool event_save_files(bool is_sram_used) return false; for (i = 0; i < task_save_files->size; i++) - content_save_ram_file(i); + content_save_ram_file(i, compress_files); return true; }