Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New Feature] Load save states from command-line or playlist #13354

Merged
merged 4 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions command.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1139,6 +1141,46 @@ 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];
Expand Down
2 changes: 2 additions & 0 deletions command.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions intl/msg_hash_us.h
Original file line number Diff line number Diff line change
Expand Up @@ -12899,3 +12899,11 @@ MSG_HASH(
"Scan Finished.<br><br>\nIn order for content to be correctly scanned, you must:\n<ul><li>have a compatible core already downloaded</li>\n<li>have \"Core Info Files\" updated via Online Updater</li>\n<li>have \"Databases\" updated via Online Updater</li>\n<li>restart RetroArch if any of the above was just done</li></ul>\nFinally, the content must match existing databases from <a href=\"https://docs.libretro.com/guides/roms-playlists-thumbnails/#sources\">here</a>. If it is still not working, consider <a href=\"https://www.github.com/libretro/RetroArch/issues\">submitting a bug report</a>."
)
#endif
MSG_HASH(
MSG_FOUND_ENTRY_STATE_IN,
"Found entry state in"
)
MSG_HASH(
MSG_LOADING_ENTRY_STATE_FROM,
"Loading entry state from"
)
13 changes: 7 additions & 6 deletions manual_content_scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions menu/cbs/menu_cbs_ok.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions msg_hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
35 changes: 29 additions & 6 deletions playlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions playlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ typedef struct
struct playlist_entry
{
char *path;
unsigned entry_slot;
char *label;
char *core_path;
char *core_name;
Expand Down
21 changes: 20 additions & 1 deletion retroarch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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 }
};

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()");
Expand Down Expand Up @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions retroarch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 22 additions & 1 deletion runloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions runloop.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
2 changes: 1 addition & 1 deletion tasks/task_content.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down