From 8526add5a882fa27def5f91e087580c8f4c2f711 Mon Sep 17 00:00:00 2001 From: carlosfritz Date: Sun, 12 Dec 2021 13:25:50 +0100 Subject: [PATCH 1/3] load save states from command-line or playlist --- command.c | 32 +++++++++++++++++++++ command.h | 4 +++ intl/msg_hash_us.h | 10 +++++++ manual_content_scan.c | 3 ++ menu/cbs/menu_cbs_ok.c | 62 ++++++++++++++++++++++++++++++++++++++++ msg_hash.h | 4 +++ paths.h | 3 ++ playlist.c | 65 ++++++++++++++++++++++++++++++++++++++++++ playlist.h | 3 ++ retroarch.c | 65 ++++++++++++++++++++++++++++++++++++++++++ runloop.c | 7 +++++ tasks/task_content.c | 41 ++++++++++++++++++++++++++ tasks/task_content.h | 3 ++ 13 files changed, 302 insertions(+) diff --git a/command.c b/command.c index 414b1591260..e8c4f7a9856 100644 --- a/command.c +++ b/command.c @@ -1082,6 +1082,10 @@ bool command_event_save_auto_state( bool ret = false; char savestate_name_auto[PATH_MAX_LENGTH]; +#ifdef HAVE_ENTRYSTATES + if (!path_is_empty(RARCH_PATH_STATE)) + return false; +#endif if (!savestate_auto_save) return false; if (current_core_type == CORE_TYPE_DUMMY) @@ -1139,6 +1143,34 @@ void command_event_init_cheats( } #endif +#ifdef HAVE_ENTRYSTATES +void command_event_load_entry_state(void) +{ + const char *state_path = path_get(RARCH_PATH_STATE); + bool ret = false; +#ifdef HAVE_CHEEVOS + if (rcheevos_hardcore_active()) + return; +#endif +#ifdef HAVE_NETWORKING + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) + return; +#endif + + if (!path_is_valid(state_path)) + return; + + ret = content_load_state(state_path, false, true); + + RARCH_LOG("%s: %s\n%s \"%s\" %s.\n", + msg_hash_to_str(MSG_FOUND_ENTRYSTATE_IN), + state_path, + msg_hash_to_str(MSG_LOADING_ENTRYSTATE_FROM), + state_path, ret ? "succeeded" : "failed" + ); +} +#endif + void command_event_load_auto_state(void) { char savestate_name_auto[PATH_MAX_LENGTH]; diff --git a/command.h b/command.h index ddb9a2dcf92..da6e8d2ce2f 100644 --- a/command.h +++ b/command.h @@ -362,6 +362,10 @@ void command_event_set_volume( void command_event_init_controllers(rarch_system_info_t *info, settings_t *settings, unsigned num_active_users); +#ifdef HAVE_ENTRYSTATES +void command_event_load_entry_state(void); +#endif + void command_event_load_auto_state(void); void command_event_set_savestate_auto_index( diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2dd83e74f78..e0e7f31fcd7 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -12899,3 +12899,13 @@ MSG_HASH( "Scan Finished.

\nIn order for content to be correctly scanned, you must:\n\nFinally, the content must match existing databases from here. If it is still not working, consider submitting a bug report." ) #endif +#ifdef HAVE_ENTRYSTATES +MSG_HASH( + MSG_FOUND_ENTRYSTATE_IN, + "Found entry state in" + ) +MSG_HASH( + MSG_LOADING_ENTRYSTATE_FROM, + "Loading entry state from" + ) +#endif diff --git a/manual_content_scan.c b/manual_content_scan.c index 466c92ffb52..13487aa6088 100644 --- a/manual_content_scan.c +++ b/manual_content_scan.c @@ -1395,6 +1395,9 @@ void manual_content_scan_add_content_to_playlist( * > The push function reads our entry as const, * so these casts are safe */ entry.path = (char*)playlist_content_path; +#ifdef HAVE_ENTRYSTATES + entry.state = '\0'; +#endif entry.label = label; entry.core_path = (char*)FILE_PATH_DETECT; entry.core_name = (char*)FILE_PATH_DETECT; diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index e7aa27432cb..63d1bf4a79d 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -2160,7 +2160,11 @@ static int default_action_ok_load_content_with_core_from_menu(const char *_path, } static int default_action_ok_load_content_from_playlist_from_menu(const char *_path, +#ifdef HAVE_ENTRYSTATES + const char *path, const char *entry_state, const char *entry_label) +#else const char *path, const char *entry_label) +#endif { content_ctx_info_t content_info; content_info.argc = 0; @@ -2168,7 +2172,11 @@ static int default_action_ok_load_content_from_playlist_from_menu(const char *_p content_info.args = NULL; content_info.environ_get = NULL; if (!task_push_load_content_from_playlist_from_menu( +#ifdef HAVE_ENTRYSTATES + _path, path, entry_state, entry_label, +#else _path, path, entry_label, +#endif &content_info, NULL, NULL)) return -1; @@ -2333,11 +2341,44 @@ static bool playlist_entry_path_is_valid(const char *entry_path) return false; } +#ifdef HAVE_ENTRYSTATES +static bool playlist_entry_state_is_valid(const char *entry_state) +{ + char *file_path = NULL; + + if (string_is_empty(entry_state)) + return true; + + file_path = strdup(entry_state); + + if (!path_is_valid(file_path)) + goto error; + + /* File is valid */ + free(file_path); + file_path = NULL; + + return true; + +error: + if (file_path) + { + free(file_path); + file_path = NULL; + } + + return false; +} +#endif + static int action_ok_playlist_entry_collection(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { playlist_config_t playlist_config; char content_path[PATH_MAX_LENGTH]; +#ifdef HAVE_ENTRYSTATES + char content_state[PATH_MAX_LENGTH]; +#endif char content_label[PATH_MAX_LENGTH]; char core_path[PATH_MAX_LENGTH]; size_t selection_ptr = entry_idx; @@ -2362,6 +2403,9 @@ static int action_ok_playlist_entry_collection(const char *path, playlist_config_set_base_content_directory(&playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); content_path[0] = '\0'; +#ifdef HAVE_ENTRYSTATES + content_state[0] = '\0'; +#endif content_label[0] = '\0'; core_path[0] = '\0'; @@ -2416,6 +2460,15 @@ static int action_ok_playlist_entry_collection(const char *path, playlist_resolve_path(PLAYLIST_LOAD, false, content_path, sizeof(content_path)); } +#ifdef HAVE_ENTRYSTATES + /* Cache entry state */ + if (!string_is_empty(entry->state)) + { + strlcpy(content_state, entry->state, sizeof(content_state)); + playlist_resolve_path(PLAYLIST_LOAD, false, content_state, sizeof(content_state)); + } +#endif + /* Cache entry label */ if (!string_is_empty(entry->label)) strlcpy(content_label, entry->label, sizeof(content_label)); @@ -2532,6 +2585,11 @@ static int action_ok_playlist_entry_collection(const char *path, if (!playlist_entry_path_is_valid(content_path)) goto error; +#ifdef HAVE_ENTRYSTATES + if (!playlist_entry_state_is_valid(content_state)) + goto error; +#endif + /* Free temporary playlist, if required */ if (playlist_initialized && tmp_playlist) { @@ -2544,7 +2602,11 @@ static int action_ok_playlist_entry_collection(const char *path, * may be free()'d by above playlist_free() - but need * to pass NULL explicitly if label is empty */ return default_action_ok_load_content_from_playlist_from_menu( +#ifdef HAVE_ENTRYSTATES + core_path, content_path, content_state, string_is_empty(content_label) ? NULL : content_label); +#else core_path, content_path, string_is_empty(content_label) ? NULL : content_label); +#endif error: runloop_msg_queue_push( diff --git a/msg_hash.h b/msg_hash.h index 74c7bb8b74e..94a32cf0934 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -499,6 +499,10 @@ enum msg_hash_enums #endif MSG_UNSUPPORTED_VIDEO_MODE, MSG_CORE_INFO_CACHE_UNSUPPORTED, +#ifdef HAVE_ENTRYSTATES + MSG_LOADING_ENTRYSTATE_FROM, + MSG_FOUND_ENTRYSTATE_IN, +#endif MENU_LABEL(MENU_XMB_ANIMATION_HORIZONTAL_HIGHLIGHT), MENU_LABEL(MENU_XMB_ANIMATION_MOVE_UP_DOWN), diff --git a/paths.h b/paths.h index 3e0134e701a..667ec5c8713 100644 --- a/paths.h +++ b/paths.h @@ -50,6 +50,9 @@ enum rarch_path_type RARCH_PATH_NAMES, RARCH_PATH_CONFIG, RARCH_PATH_CONTENT, +#ifdef HAVE_ENTRYSTATES + RARCH_PATH_STATE, +#endif RARCH_PATH_CONFIG_APPEND, RARCH_PATH_CORE_OPTIONS, RARCH_PATH_DEFAULT_SHADER_PRESET, diff --git a/playlist.c b/playlist.c index 8391dcf1153..8cde2cbe1bf 100644 --- a/playlist.c +++ b/playlist.c @@ -43,8 +43,12 @@ #endif #ifndef PLAYLIST_ENTRIES +#ifdef HAVE_ENTRYSTATES +#define PLAYLIST_ENTRIES 7 +#else #define PLAYLIST_ENTRIES 6 #endif +#endif #define WINDOWS_PATH_DELIMITER '\\' #define POSIX_PATH_DELIMITER '/' @@ -1356,6 +1360,14 @@ bool playlist_push(playlist_t *playlist, continue; } +#ifdef HAVE_ENTRYSTATES + if (playlist->entries[i].state != entry->state) + { + playlist->entries[i].state = strdup(entry->state); + entry_updated = true; + } +#endif + /* If content was previously loaded via file browser * or command line, certain entry values will be missing. * If we are now loading the same content from a playlist, @@ -1445,6 +1457,12 @@ bool playlist_push(playlist_t *playlist, playlist->entries[0].path_id = path_id; path_id = NULL; +#ifdef HAVE_ENTRYSTATES + playlist->entries[0].state = NULL; + + if (!string_is_empty(entry->state)) + playlist->entries[0].state = strdup(entry->state); +#endif if (!string_is_empty(entry->label)) playlist->entries[0].label = strdup(entry->label); if (!string_is_empty(real_core_path)) @@ -1689,6 +1707,9 @@ void playlist_write_file(playlist_t *playlist) for (i = 0, len = RBUF_LEN(playlist->entries); i < len; i++) intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n", playlist->entries[i].path ? playlist->entries[i].path : "", +#ifdef HAVE_ENTRYSTATES + playlist->entries[i].state ? playlist->entries[i].state : "", +#endif playlist->entries[i].label ? playlist->entries[i].label : "", playlist->entries[i].core_path ? playlist->entries[i].core_path : "", playlist->entries[i].core_name ? playlist->entries[i].core_name : "", @@ -1873,6 +1894,16 @@ void playlist_write_file(playlist_t *playlist) rjsonwriter_add_string(writer, playlist->entries[i].path); rjsonwriter_add_comma(writer); +#ifdef HAVE_ENTRYSTATES + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "state"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_string(writer, playlist->entries[i].state); + rjsonwriter_add_comma(writer); +#endif + rjsonwriter_add_newline(writer); rjsonwriter_add_spaces(writer, 6); rjsonwriter_add_string(writer, "label"); @@ -2363,6 +2394,10 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le pCtx->current_string_val = &pCtx->current_entry->subsystem_name; else if (string_is_equal(pValue, "subsystem_roms")) pCtx->in_subsystem_roms = true; +#ifdef HAVE_ENTRYSTATES + else if (string_is_equal(pValue, "state")) + pCtx->current_string_val = &pCtx->current_entry->state; +#endif break; } } @@ -2601,6 +2636,35 @@ static bool playlist_read_file(playlist_t *playlist) memset(entry, 0, sizeof(*entry)); +#ifdef HAVE_ENTRYSTATES + /* path */ + if (!string_is_empty(line_buf[0])) + entry->path = strdup(line_buf[0]); + + /* state */ + if (!string_is_empty(line_buf[1])) + entry->state = strdup(line_buf[1]); + + /* label */ + if (!string_is_empty(line_buf[2])) + entry->label = strdup(line_buf[2]); + + /* core_path */ + if (!string_is_empty(line_buf[3])) + entry->core_path = strdup(line_buf[3]); + + /* core_name */ + if (!string_is_empty(line_buf[4])) + entry->core_name = strdup(line_buf[4]); + + /* crc32 */ + if (!string_is_empty(line_buf[5])) + entry->crc32 = strdup(line_buf[5]); + + /* db_name */ + if (!string_is_empty(line_buf[6])) + entry->db_name = strdup(line_buf[6]); +#else /* path */ if (!string_is_empty(line_buf[0])) entry->path = strdup(line_buf[0]); @@ -2624,6 +2688,7 @@ static bool playlist_read_file(playlist_t *playlist) /* db_name */ if (!string_is_empty(line_buf[5])) entry->db_name = strdup(line_buf[5]); +#endif } /* If fewer than 'PLAYLIST_ENTRIES' lines were * read, then this is metadata */ diff --git a/playlist.h b/playlist.h index cfd74fbc810..95e2bdf5a63 100644 --- a/playlist.h +++ b/playlist.h @@ -101,6 +101,9 @@ typedef struct struct playlist_entry { char *path; +#ifdef HAVE_ENTRYSTATES + char *state; +#endif char *label; char *core_path; char *core_name; diff --git a/retroarch.c b/retroarch.c index 0f9151d2d19..62ceccd9193 100644 --- a/retroarch.c +++ b/retroarch.c @@ -300,6 +300,9 @@ struct rarch_state char launch_arguments[4096]; char path_default_shader_preset[PATH_MAX_LENGTH]; char path_content[PATH_MAX_LENGTH]; +#ifdef HAVE_ENTRYSTATES + char path_state[PATH_MAX_LENGTH]; +#endif char path_libretro[PATH_MAX_LENGTH]; char path_config_file[PATH_MAX_LENGTH]; char path_config_append_file[256]; @@ -963,6 +966,10 @@ char *path_get_ptr(enum rarch_path_type type) { case RARCH_PATH_CONTENT: return p_rarch->path_content; +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + return p_rarch->path_state; +#endif case RARCH_PATH_DEFAULT_SHADER_PRESET: return p_rarch->path_default_shader_preset; case RARCH_PATH_BASENAME: @@ -1000,6 +1007,10 @@ const char *path_get(enum rarch_path_type type) { case RARCH_PATH_CONTENT: return p_rarch->path_content; +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + return p_rarch->path_state; +#endif case RARCH_PATH_DEFAULT_SHADER_PRESET: return p_rarch->path_default_shader_preset; case RARCH_PATH_BASENAME: @@ -1037,6 +1048,10 @@ size_t path_get_realsize(enum rarch_path_type type) { case RARCH_PATH_CONTENT: return sizeof(p_rarch->path_content); +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + return sizeof(p_rarch->path_state); +#endif case RARCH_PATH_DEFAULT_SHADER_PRESET: return sizeof(p_rarch->path_default_shader_preset); case RARCH_PATH_BASENAME: @@ -1106,6 +1121,12 @@ bool path_set(enum rarch_path_type type, const char *path) strlcpy(p_rarch->path_content, path, sizeof(p_rarch->path_content)); break; +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + strlcpy(p_rarch->path_state, path, + sizeof(p_rarch->path_state)); + break; +#endif case RARCH_PATH_NONE: break; } @@ -1144,6 +1165,12 @@ bool path_is_empty(enum rarch_path_type type) if (string_is_empty(p_rarch->path_content)) return true; break; +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + if (string_is_empty(p_rarch->path_state)) + return true; + break; +#endif case RARCH_PATH_CORE: if (string_is_empty(p_rarch->path_libretro)) return true; @@ -1179,6 +1206,11 @@ void path_clear(enum rarch_path_type type) case RARCH_PATH_CONTENT: *p_rarch->path_content = '\0'; break; +#ifdef HAVE_ENTRYSTATES + case RARCH_PATH_STATE: + *p_rarch->path_state = '\0'; + break; +#endif case RARCH_PATH_BASENAME: *runloop_st->runtime_content_path_basename = '\0'; break; @@ -1200,6 +1232,9 @@ void path_clear(enum rarch_path_type type) static void path_clear_all(void) { path_clear(RARCH_PATH_CONTENT); +#ifdef HAVE_ENTRYSTATES + path_clear(RARCH_PATH_STATE); +#endif path_clear(RARCH_PATH_CONFIG); path_clear(RARCH_PATH_CONFIG_APPEND); path_clear(RARCH_PATH_CORE_OPTIONS); @@ -4214,6 +4249,10 @@ static void retroarch_print_help(const char *arg0) #endif strlcat(buf, " --load-menu-on-error\n" " Open menu instead of quitting if specified core or content fails to load.\n", sizeof(buf)); +#ifdef HAVE_ENTRYSTATES + strlcat(buf, " -e, --entrystate=FILE\n" + " Automatically load save state\n", sizeof(buf)); +#endif puts(buf); } } @@ -4302,6 +4341,9 @@ static bool retroarch_parse_input_and_config( { "log-file", 1, NULL, RA_OPT_LOG_FILE }, { "accessibility", 0, NULL, RA_OPT_ACCESSIBILITY}, { "load-menu-on-error", 0, NULL, RA_OPT_LOAD_MENU_ON_ERROR }, +#ifdef HAVE_ENTRYSTATES + { "entrystate", 1, NULL, 'e' }, +#endif { NULL, 0, NULL, 0 } }; @@ -4372,7 +4414,11 @@ static bool retroarch_parse_input_and_config( /* Make sure we can call retroarch_parse_input several times ... */ optind = 0; +#ifdef HAVE_ENTRYSTATES + optstring = "hs:fvS:A:U:DN:d:e:" +#else optstring = "hs:fvS:A:U:DN:d:" +#endif BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG; #if defined(ORBIS) @@ -4872,6 +4918,18 @@ static bool retroarch_parse_input_and_config( case RA_OPT_LOAD_MENU_ON_ERROR: global->cli_load_menu_on_error = true; break; +#ifdef HAVE_ENTRYSTATES + case 'e': + { + int path_stats = path_stat(optarg); + + if ((path_stats & RETRO_VFS_STAT_IS_VALID) != 0 && (path_stats & RETRO_VFS_STAT_IS_DIRECTORY) == 0) + path_set(RARCH_PATH_STATE, optarg); + else + RARCH_WARN("--entrystate argument \"%s\" is not a file. Ignoring.\n", optarg); + } + break; +#endif default: RARCH_ERR("%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS)); retroarch_fail(1, "retroarch_parse_input()"); @@ -4920,6 +4978,13 @@ static bool retroarch_parse_input_and_config( * command line interface */ cli_content_set = true; } +#ifdef HAVE_ENTRYSTATES + else if (!path_is_empty(RARCH_PATH_STATE)) + { + path_clear(RARCH_PATH_STATE); + RARCH_WARN("Trying to load entry state without content. Ignoring.\n"); + } +#endif /* Check whether a core has been set via the * command line interface */ diff --git a/runloop.c b/runloop.c index 2e012c50837..5a5c4b561ef 100644 --- a/runloop.c +++ b/runloop.c @@ -5104,9 +5104,16 @@ static bool event_init_content( */ #ifdef HAVE_CHEEVOS if (!cheevos_enable || !cheevos_hardcore_mode_enable) +#endif + { +#ifdef HAVE_ENTRYSTATES + if (!path_is_empty(RARCH_PATH_STATE)) + command_event_load_entry_state(); + else #endif if (settings->bools.savestate_auto_load) command_event_load_auto_state(); + } #ifdef HAVE_BSV_MOVIE bsv_movie_deinit(input_st); diff --git a/tasks/task_content.c b/tasks/task_content.c index 72e3f539d66..f1fc29fdda5 100644 --- a/tasks/task_content.c +++ b/tasks/task_content.c @@ -1686,6 +1686,22 @@ static void task_push_to_history_list( content_get_subsystem_friendly_name(path_get(RARCH_PATH_SUBSYSTEM), subsystem_name, sizeof(subsystem_name)); +#ifdef HAVE_ENTRYSTATES + char state[PATH_MAX_LENGTH]; + const char *path_state = path_get(RARCH_PATH_STATE); + + state[0] = '\0'; + + if (!string_is_empty(path_state)) + strlcpy(state, path_state, sizeof(state)); + + /* Path can be relative here. + * Ensure we're pushing absolute path. */ + if (!launched_from_menu && !string_is_empty(state)) + path_resolve_realpath(state, sizeof(state), true); + + entry.state = (char*)state; +#endif /* The push function reads our entry as const, * so these casts are safe */ entry.path = (char*)tmp; @@ -1708,6 +1724,9 @@ static void task_push_to_history_list( static bool command_event_cmd_exec( content_state_t *p_content, const char *data, +#ifdef HAVE_ENTRYSTATES + const char *state, +#endif content_information_ctx_t *content_ctx, bool launched_from_cli, char **error_string) @@ -1719,6 +1738,15 @@ static bool command_event_cmd_exec( path_set(RARCH_PATH_CONTENT, data); } +#ifdef HAVE_ENTRYSTATES + if (path_get(RARCH_PATH_STATE) != state) + { + path_clear(RARCH_PATH_STATE); + if (!string_is_empty(state)) + path_set(RARCH_PATH_STATE, state); + } +#endif + #if defined(HAVE_DYNAMIC) { content_ctx_info_t content_info; @@ -1885,6 +1913,9 @@ bool task_push_start_dummy_core(content_ctx_info_t *content_info) bool task_push_load_content_from_playlist_from_menu( const char *core_path, const char *fullpath, +#ifdef HAVE_ENTRYSTATES + const char *state, +#endif const char *label, content_ctx_info_t *content_info, retro_task_callback_t cb, @@ -1959,6 +1990,12 @@ bool task_push_load_content_from_playlist_from_menu( if (!string_is_empty(fullpath)) path_set(RARCH_PATH_CONTENT, fullpath); +#ifdef HAVE_ENTRYSTATES + path_clear(RARCH_PATH_STATE); + if (!string_is_empty(state)) + path_set(RARCH_PATH_STATE, state); +#endif + /* Load content */ ret = content_load(content_info, p_content); @@ -1983,7 +2020,11 @@ bool task_push_load_content_from_playlist_from_menu( * > On targets that do not support dynamic core loading, * command_event_cmd_exec() will fork a new instance */ if (!(ret = command_event_cmd_exec(p_content, +#ifdef HAVE_ENTRYSTATES + fullpath, state, &content_ctx, false, &error_string))) +#else fullpath, &content_ctx, false, &error_string))) +#endif goto end; #ifdef HAVE_COCOATOUCH diff --git a/tasks/task_content.h b/tasks/task_content.h index 34289e24dc7..f601bbd7889 100644 --- a/tasks/task_content.h +++ b/tasks/task_content.h @@ -95,6 +95,9 @@ bool task_push_load_content_with_new_core_from_menu( bool task_push_load_content_from_playlist_from_menu( const char *core_path, const char *fullpath, +#ifdef HAVE_ENTRYSTATES + const char *state, +#endif const char *label, content_ctx_info_t *content_info, retro_task_callback_t cb, From 67baa292dad0289cadf6a386a935a00c2b9831dd Mon Sep 17 00:00:00 2001 From: carlosfritz Date: Sun, 12 Dec 2021 13:25:50 +0100 Subject: [PATCH 2/3] load save states from command-line or playlist --- command.c | 44 ++++++++++++++++++++++++++++++++++++++++++ command.h | 2 ++ intl/msg_hash_us.h | 8 ++++++++ manual_content_scan.c | 13 +++++++------ menu/cbs/menu_cbs_ok.c | 3 +++ msg_hash.h | 2 ++ playlist.c | 35 +++++++++++++++++++++++++++------ playlist.h | 1 + retroarch.c | 21 +++++++++++++++++++- retroarch.h | 2 ++ runloop.c | 23 +++++++++++++++++++++- runloop.h | 1 + tasks/task_content.c | 2 +- 13 files changed, 142 insertions(+), 15 deletions(-) diff --git a/command.c b/command.c index 414b1591260..e80f6192845 100644 --- a/command.c +++ b/command.c @@ -1082,6 +1082,8 @@ bool command_event_save_auto_state( bool ret = false; char savestate_name_auto[PATH_MAX_LENGTH]; + if (runloop_st->entry_state_slot) + return false; if (!savestate_auto_save) return false; if (current_core_type == CORE_TYPE_DUMMY) @@ -1139,6 +1141,48 @@ void command_event_init_cheats( } #endif +bool command_event_load_entry_state(void) +{ + char entry_state_path[PATH_MAX_LENGTH]; + int entry_path_stats; + runloop_state_t *runloop_st = runloop_state_get_ptr(); + bool ret = false; + +#ifdef HAVE_CHEEVOS + if (rcheevos_hardcore_active()) + return false; +#endif +#ifdef HAVE_NETWORKING + if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) + return false; +#endif + + entry_state_path[0] = '\0'; + + if (!retroarch_get_entry_state_path(entry_state_path, sizeof(entry_state_path), + runloop_st->entry_state_slot)) + return false; + + entry_path_stats = path_stat(entry_state_path); + + if ((entry_path_stats & RETRO_VFS_STAT_IS_VALID) == 0 + || (entry_path_stats & RETRO_VFS_STAT_IS_DIRECTORY) != 0) + { + return false; + } + + ret = content_load_state(entry_state_path, false, true); + + RARCH_LOG("%s: %s\n%s \"%s\" %s.\n", + msg_hash_to_str(MSG_FOUND_ENTRY_STATE_IN), + entry_state_path, + msg_hash_to_str(MSG_LOADING_ENTRY_STATE_FROM), + entry_state_path, ret ? "succeeded" : "failed" + ); + + return ret; +} + void command_event_load_auto_state(void) { char savestate_name_auto[PATH_MAX_LENGTH]; diff --git a/command.h b/command.h index ddb9a2dcf92..395e9d07484 100644 --- a/command.h +++ b/command.h @@ -362,6 +362,8 @@ void command_event_set_volume( void command_event_init_controllers(rarch_system_info_t *info, settings_t *settings, unsigned num_active_users); +bool command_event_load_entry_state(void); + void command_event_load_auto_state(void); void command_event_set_savestate_auto_index( diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2dd83e74f78..e186733ba34 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -12899,3 +12899,11 @@ MSG_HASH( "Scan Finished.

\nIn order for content to be correctly scanned, you must:\n\nFinally, the content must match existing databases from here. If it is still not working, consider submitting a bug report." ) #endif +MSG_HASH( + MSG_FOUND_ENTRY_STATE_IN, + "Found entry state in" + ) +MSG_HASH( + MSG_LOADING_ENTRY_STATE_FROM, + "Loading entry state from" + ) diff --git a/manual_content_scan.c b/manual_content_scan.c index 466c92ffb52..cb394b34a2a 100644 --- a/manual_content_scan.c +++ b/manual_content_scan.c @@ -1394,12 +1394,13 @@ void manual_content_scan_add_content_to_playlist( /* Configure playlist entry * > The push function reads our entry as const, * so these casts are safe */ - entry.path = (char*)playlist_content_path; - entry.label = label; - entry.core_path = (char*)FILE_PATH_DETECT; - entry.core_name = (char*)FILE_PATH_DETECT; - entry.crc32 = (char*)"00000000|crc"; - entry.db_name = task_config->database_name; + entry.path = (char*)playlist_content_path; + entry.entry_slot = 0; + entry.label = label; + entry.core_path = (char*)FILE_PATH_DETECT; + entry.core_name = (char*)FILE_PATH_DETECT; + entry.crc32 = (char*)"00000000|crc"; + entry.db_name = task_config->database_name; /* Add entry to playlist */ playlist_push(playlist, &entry); diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index e7aa27432cb..aefdd33b83a 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -2349,6 +2349,7 @@ static int action_ok_playlist_entry_collection(const char *path, bool core_is_builtin = false; menu_handle_t *menu = menu_state_get_ptr()->driver_data; settings_t *settings = config_get_ptr(); + runloop_state_t *runloop_st = runloop_state_get_ptr(); bool playlist_sort_alphabetical = settings->bools.playlist_sort_alphabetical; const char *path_content_history = settings->paths.path_content_history; const char *path_content_image_history = settings->paths.path_content_image_history; @@ -2416,6 +2417,8 @@ static int action_ok_playlist_entry_collection(const char *path, playlist_resolve_path(PLAYLIST_LOAD, false, content_path, sizeof(content_path)); } + runloop_st->entry_state_slot = entry->entry_slot; + /* Cache entry label */ if (!string_is_empty(entry->label)) strlcpy(content_label, entry->label, sizeof(content_label)); diff --git a/msg_hash.h b/msg_hash.h index 74c7bb8b74e..910e36f1f71 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -499,6 +499,8 @@ enum msg_hash_enums #endif MSG_UNSUPPORTED_VIDEO_MODE, MSG_CORE_INFO_CACHE_UNSUPPORTED, + MSG_LOADING_ENTRY_STATE_FROM, + MSG_FOUND_ENTRY_STATE_IN, MENU_LABEL(MENU_XMB_ANIMATION_HORIZONTAL_HIGHLIGHT), MENU_LABEL(MENU_XMB_ANIMATION_MOVE_UP_DOWN), diff --git a/playlist.c b/playlist.c index 8391dcf1153..e15911e9501 100644 --- a/playlist.c +++ b/playlist.c @@ -1356,24 +1356,30 @@ bool playlist_push(playlist_t *playlist, continue; } + if (playlist->entries[i].entry_slot != entry->entry_slot) + { + playlist->entries[i].entry_slot = entry->entry_slot; + entry_updated = true; + } + /* If content was previously loaded via file browser * or command line, certain entry values will be missing. * If we are now loading the same content from a playlist, * fill in any blanks */ if (!playlist->entries[i].label && !string_is_empty(entry->label)) { - playlist->entries[i].label = strdup(entry->label); - entry_updated = true; + playlist->entries[i].label = strdup(entry->label); + entry_updated = true; } if (!playlist->entries[i].crc32 && !string_is_empty(entry->crc32)) { - playlist->entries[i].crc32 = strdup(entry->crc32); - entry_updated = true; + playlist->entries[i].crc32 = strdup(entry->crc32); + entry_updated = true; } if (!playlist->entries[i].db_name && !string_is_empty(entry->db_name)) { - playlist->entries[i].db_name = strdup(entry->db_name); - entry_updated = true; + playlist->entries[i].db_name = strdup(entry->db_name); + entry_updated = true; } /* If top entry, we don't want to push a new entry since @@ -1445,6 +1451,8 @@ bool playlist_push(playlist_t *playlist, playlist->entries[0].path_id = path_id; path_id = NULL; + playlist->entries[0].entry_slot = entry->entry_slot; + if (!string_is_empty(entry->label)) playlist->entries[0].label = strdup(entry->label); if (!string_is_empty(real_core_path)) @@ -1873,6 +1881,17 @@ void playlist_write_file(playlist_t *playlist) rjsonwriter_add_string(writer, playlist->entries[i].path); rjsonwriter_add_comma(writer); + if (playlist->entries[i].entry_slot) + { + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "entry_slot"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_int(writer, (int)playlist->entries[i].entry_slot); + rjsonwriter_add_comma(writer); + } + rjsonwriter_add_newline(writer); rjsonwriter_add_spaces(writer, 6); rjsonwriter_add_string(writer, "label"); @@ -2328,6 +2347,10 @@ static bool JSONObjectMemberHandler(void *context, const char *pValue, size_t le if (string_is_equal(pValue, "db_name")) pCtx->current_string_val = &pCtx->current_entry->db_name; break; + case 'e': + if (string_is_equal(pValue, "entry_slot")) + pCtx->current_entry_uint_val = &pCtx->current_entry->entry_slot; + break; case 'l': if (string_is_equal(pValue, "label")) pCtx->current_string_val = &pCtx->current_entry->label; diff --git a/playlist.h b/playlist.h index cfd74fbc810..ebf5374ceda 100644 --- a/playlist.h +++ b/playlist.h @@ -101,6 +101,7 @@ typedef struct struct playlist_entry { char *path; + unsigned entry_slot; char *label; char *core_path; char *core_name; diff --git a/retroarch.c b/retroarch.c index 0f9151d2d19..4c31631aba6 100644 --- a/retroarch.c +++ b/retroarch.c @@ -4214,6 +4214,8 @@ static void retroarch_print_help(const char *arg0) #endif strlcat(buf, " --load-menu-on-error\n" " Open menu instead of quitting if specified core or content fails to load.\n", sizeof(buf)); + strlcat(buf, " -e, --entryslot=NUMBER\n" + " Slot from which to load an entry state\n", sizeof(buf)); puts(buf); } } @@ -4302,6 +4304,7 @@ static bool retroarch_parse_input_and_config( { "log-file", 1, NULL, RA_OPT_LOG_FILE }, { "accessibility", 0, NULL, RA_OPT_ACCESSIBILITY}, { "load-menu-on-error", 0, NULL, RA_OPT_LOAD_MENU_ON_ERROR }, + { "entryslot", 1, NULL, 'e' }, { NULL, 0, NULL, 0 } }; @@ -4372,7 +4375,7 @@ static bool retroarch_parse_input_and_config( /* Make sure we can call retroarch_parse_input several times ... */ optind = 0; - optstring = "hs:fvS:A:U:DN:d:" + optstring = "hs:fvS:A:U:DN:d:e:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG; #if defined(ORBIS) @@ -4872,6 +4875,17 @@ static bool retroarch_parse_input_and_config( case RA_OPT_LOAD_MENU_ON_ERROR: global->cli_load_menu_on_error = true; break; + case 'e': + { + unsigned entry_state_slot = (unsigned)strtoul(optarg, NULL, 0); + + if (entry_state_slot) + runloop_st->entry_state_slot = entry_state_slot; + else + RARCH_WARN("--entryslot argument \"%s\" is not a valid " + "entry state slot index. Ignoring.\n", optarg); + } + break; default: RARCH_ERR("%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS)); retroarch_fail(1, "retroarch_parse_input()"); @@ -4920,6 +4934,11 @@ static bool retroarch_parse_input_and_config( * command line interface */ cli_content_set = true; } + else if (runloop_st->entry_state_slot) + { + runloop_st->entry_state_slot = 0; + RARCH_WARN("Trying to load entry state without content. Ignoring.\n"); + } /* Check whether a core has been set via the * command line interface */ diff --git a/retroarch.h b/retroarch.h index f02017b771b..e772ad18e8b 100644 --- a/retroarch.h +++ b/retroarch.h @@ -197,6 +197,8 @@ typedef enum apple_view_type bool retroarch_get_current_savestate_path(char *path, size_t len); +bool retroarch_get_entry_state_path(char *path, size_t len, unsigned slot); + /** * retroarch_fail: * @error_code : Error code. diff --git a/runloop.c b/runloop.c index 2e012c50837..638af480be2 100644 --- a/runloop.c +++ b/runloop.c @@ -5105,8 +5105,12 @@ static bool event_init_content( #ifdef HAVE_CHEEVOS if (!cheevos_enable || !cheevos_hardcore_mode_enable) #endif - if (settings->bools.savestate_auto_load) + { + if (runloop_st->entry_state_slot && !command_event_load_entry_state()) + runloop_st->entry_state_slot = 0; + if (!runloop_st->entry_state_slot && settings->bools.savestate_auto_load) command_event_load_auto_state(); + } #ifdef HAVE_BSV_MOVIE bsv_movie_deinit(input_st); @@ -7829,6 +7833,23 @@ bool retroarch_get_current_savestate_path(char *path, size_t len) return true; } +bool retroarch_get_entry_state_path(char *path, size_t len, unsigned slot) +{ + runloop_state_t *runloop_st = &runloop_state; + const char *name_savestate = NULL; + + if (!path || !slot) + return false; + + name_savestate = runloop_st->name.savestate; + if (string_is_empty(name_savestate)) + return false; + + snprintf(path, len, "%s%d%s", name_savestate, slot, ".entry"); + + return true; +} + void runloop_set_current_core_type( enum rarch_core_type type, bool explicitly_set) { diff --git a/runloop.h b/runloop.h index 9883fb617ca..2beb9b8048c 100644 --- a/runloop.h +++ b/runloop.h @@ -218,6 +218,7 @@ struct runloop unsigned fastforward_after_frames; unsigned perf_ptr_libretro; unsigned subsystem_current_count; + unsigned entry_state_slot; fastmotion_overrides_t fastmotion_override; /* float alignment */ diff --git a/tasks/task_content.c b/tasks/task_content.c index 72e3f539d66..5089d8345f4 100644 --- a/tasks/task_content.c +++ b/tasks/task_content.c @@ -1685,10 +1685,10 @@ static void task_push_to_history_list( subsystem_name[0] = '\0'; content_get_subsystem_friendly_name(path_get(RARCH_PATH_SUBSYSTEM), subsystem_name, sizeof(subsystem_name)); - /* The push function reads our entry as const, * so these casts are safe */ entry.path = (char*)tmp; + entry.entry_slot = runloop_st->entry_state_slot; entry.label = (char*)label; entry.core_path = (char*)core_path; entry.core_name = (char*)core_name; From 3f81d63775b038811080e58ebb1d6ef4cd956d7c Mon Sep 17 00:00:00 2001 From: carlosfritz Date: Mon, 13 Dec 2021 21:32:02 +0100 Subject: [PATCH 3/3] removed some brackets --- command.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/command.c b/command.c index e80f6192845..a832a791ca1 100644 --- a/command.c +++ b/command.c @@ -1167,9 +1167,7 @@ bool command_event_load_entry_state(void) if ((entry_path_stats & RETRO_VFS_STAT_IS_VALID) == 0 || (entry_path_stats & RETRO_VFS_STAT_IS_DIRECTORY) != 0) - { return false; - } ret = content_load_state(entry_state_path, false, true);