diff --git a/CHANGES.md b/CHANGES.md
index b252ae132342..1dc68816b6f8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -36,6 +36,8 @@
- INPUT/BSV/REPLAY: Add checkpoint and initial savestate compression, following the `savestate_file_compression` config boolean. Use zstd if available, or fall back to zlib.
- INPUT/BSV/REPLAY: Add incremental checkpoints based on statestreams (depending on `HAVE_STATESTREAM` compile time flag). As an example, 60 `pcsx_rearmed` savestates would take 267MB uncompressed; with incremental encoding this is reduced to 77MB. Compressing the result can reduce the size to just 4MB.
- INPUT/BSV/REPLAY: Checkpoint compression and encoding can be combined. For example, 60 `pcsx_rearmed` checkpoints can take up just 15MB if each state is incremental and compressed. This is not as optimal as using incremental states without save state compression followed by offline compression, but is a good compromise in many use cases.
+- INPUT/BSV/REPLAY: Add hotkeys and text commands to force a checkpoint insertion into the currently recording replay, and to seek backwards to the previous checkpoint and forwards to the next checkpoint.
+- INPUT/BSV/REPLAY: Add a text command to seek to a specific frame of the currently playing/recording replay; it will return via the command replier the actual seeked-to frame (right now it only supports seeking to checkpoints).
- INTL: Add Irish Gaelic to selectable languages
- IOS: Fix crash on iOS9 when fetching refresh rate
- LINUX: Add full complement of key/value pairs to desktop entry
diff --git a/Makefile.common b/Makefile.common
index 3a6dc9685ce2..411f959f392e 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -25,10 +25,6 @@ ifeq ($(HAVE_SAPI), 1)
LIBS += sapi.dll
endif
-ifeq ($(HAVE_STATESTREAM), 1)
- DEF_FLAGS += -DHAVE_STATESTREAM
-endif
-
ifeq ($(HAVE_GL_CONTEXT),)
HAVE_GL_CONTEXT = 0
HAVE_GL_MODERN = 0
@@ -455,8 +451,12 @@ endif
ifeq ($(HAVE_BSV_MOVIE), 1)
DEFINES += -DHAVE_BSV_MOVIE
- OBJ += input/bsv/bsvmovie.o \
- input/bsv/uint32s_index.o
+ OBJ += input/bsv/bsvmovie.o
+endif
+
+ifeq ($(HAVE_STATESTREAM), 1)
+ DEFINES += -DHAVE_STATESTREAM
+ OBJ += input/bsv/uint32s_index.o
endif
ifeq ($(HAVE_RUNAHEAD), 1)
diff --git a/command.c b/command.c
index 4cd95c136ea9..6e3e1a111562 100644
--- a/command.c
+++ b/command.c
@@ -15,6 +15,7 @@
* If not, see .
*/
+#include "input/input_driver.h"
#include
#include
#include
@@ -802,6 +803,42 @@ bool command_play_replay_slot(command_t *cmd, const char *arg)
#endif
}
+bool command_seek_replay(command_t *cmd, const char *arg)
+{
+#ifdef HAVE_BSV_MOVIE
+ char reply[32];
+ bool ret = true;
+ char *endptr;
+ size_t _len;
+ int64_t frame = strtoll(arg, &endptr, 10), target_frame;
+ input_driver_state_t *input_st = input_state_get_ptr();
+ if (!endptr)
+ ret = false;
+ if (!(input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_PLAYBACK | BSV_FLAG_MOVIE_RECORDING)))
+ ret = false;
+#ifdef HAVE_CHEEVOS
+ ret = !rcheevos_hardcore_active();
+#endif
+ if (ret)
+ ret = movie_seek_to_frame(input_st, frame);
+ if (ret)
+ {
+ _len = strlcpy(reply, "OK ", sizeof(reply));
+ _len += snprintf(reply+_len, sizeof(reply)-_len,
+ "%ld", input_st->bsv_movie_state.seek_target_frame);
+ }
+ else
+ _len = strlcpy(reply, "NO", sizeof(reply));
+ reply[_len] = '\n';
+ reply[++_len] = '\0';
+ cmd->replier(cmd, reply, _len);
+ return ret;
+#else
+ cmd->replier(cmd, "NO\n", 4);
+ return false;
+#endif
+}
+
bool command_save_savefiles(command_t *cmd, const char* arg)
{
char reply[4];
diff --git a/command.h b/command.h
index 6f4564acb4b6..7d2c01a710d6 100644
--- a/command.h
+++ b/command.h
@@ -69,6 +69,9 @@ enum event_command
CMD_EVENT_PLAY_REPLAY,
CMD_EVENT_RECORD_REPLAY,
CMD_EVENT_HALT_REPLAY,
+ CMD_EVENT_SAVE_REPLAY_CHECKPOINT,
+ CMD_EVENT_PREV_REPLAY_CHECKPOINT,
+ CMD_EVENT_NEXT_REPLAY_CHECKPOINT,
CMD_EVENT_REPLAY_DECREMENT,
CMD_EVENT_REPLAY_INCREMENT,
/* Save state actions. */
@@ -421,6 +424,7 @@ bool command_get_config_param(command_t *cmd, const char* arg);
bool command_show_osd_msg(command_t *cmd, const char* arg);
bool command_load_state_slot(command_t *cmd, const char* arg);
bool command_play_replay_slot(command_t *cmd, const char* arg);
+bool command_seek_replay(command_t *cmd, const char *arg);
bool command_save_savefiles(command_t *cmd, const char* arg);
bool command_load_savefiles(command_t *cmd, const char* arg);
#ifdef HAVE_CHEEVOS
@@ -449,6 +453,7 @@ static const struct cmd_action_map action_map[] = {
{ "LOAD_STATE_SLOT",command_load_state_slot, ""},
{ "PLAY_REPLAY_SLOT",command_play_replay_slot, ""},
+ { "SEEK_REPLAY",command_seek_replay, ""},
{ "SAVE_FILES", command_save_savefiles, "No argument"},
{ "LOAD_FILES", command_load_savefiles, "No argument"},
@@ -480,6 +485,9 @@ static const struct cmd_map map[] = {
{ "PLAY_REPLAY", RARCH_PLAY_REPLAY_KEY },
{ "RECORD_REPLAY", RARCH_RECORD_REPLAY_KEY },
{ "HALT_REPLAY", RARCH_HALT_REPLAY_KEY },
+ { "SAVE_REPLAY_CHECKPOINT", RARCH_SAVE_REPLAY_CHECKPOINT_KEY },
+ { "PREV_REPLAY_CHECKPOINT", RARCH_PREV_REPLAY_CHECKPOINT_KEY },
+ { "NEXT_REPLAY_CHECKPOINT", RARCH_NEXT_REPLAY_CHECKPOINT_KEY },
{ "REPLAY_SLOT_PLUS", RARCH_REPLAY_SLOT_PLUS },
{ "REPLAY_SLOT_MINUS", RARCH_REPLAY_SLOT_MINUS },
diff --git a/config.def.keybinds.h b/config.def.keybinds.h
index e6190e0db893..e16abc03d658 100644
--- a/config.def.keybinds.h
+++ b/config.def.keybinds.h
@@ -430,6 +430,27 @@ static const struct retro_keybind retro_keybinds_1[] = {
RARCH_HALT_REPLAY_KEY, NO_BTN, NO_BTN, 0,
true
},
+ {
+ NULL, NULL,
+ AXIS_NONE, AXIS_NONE,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY, RETROK_UNKNOWN,
+ RARCH_SAVE_REPLAY_CHECKPOINT_KEY, NO_BTN, NO_BTN, 0,
+ true
+ },
+ {
+ NULL, NULL,
+ AXIS_NONE, AXIS_NONE,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY, RETROK_UNKNOWN,
+ RARCH_PREV_REPLAY_CHECKPOINT_KEY, NO_BTN, NO_BTN, 0,
+ true
+ },
+ {
+ NULL, NULL,
+ AXIS_NONE, AXIS_NONE,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY, RETROK_UNKNOWN,
+ RARCH_NEXT_REPLAY_CHECKPOINT_KEY, NO_BTN, NO_BTN, 0,
+ true
+ },
{
NULL, NULL,
AXIS_NONE, AXIS_NONE,
diff --git a/configuration.c b/configuration.c
index abe9d63d45b8..b32156cd4536 100644
--- a/configuration.c
+++ b/configuration.c
@@ -366,6 +366,9 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = {
DECLARE_META_BIND(1, play_replay, RARCH_PLAY_REPLAY_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_PLAY_REPLAY_KEY),
DECLARE_META_BIND(1, record_replay, RARCH_RECORD_REPLAY_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_RECORD_REPLAY_KEY),
DECLARE_META_BIND(1, halt_replay, RARCH_HALT_REPLAY_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_HALT_REPLAY_KEY),
+ DECLARE_META_BIND(1, save_replay_checkpoint,RARCH_SAVE_REPLAY_CHECKPOINT_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY),
+ DECLARE_META_BIND(1, prev_replay_checkpoint,RARCH_PREV_REPLAY_CHECKPOINT_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY),
+ DECLARE_META_BIND(1, next_replay_checkpoint,RARCH_NEXT_REPLAY_CHECKPOINT_KEY, MENU_ENUM_LABEL_VALUE_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY),
DECLARE_META_BIND(2, replay_slot_increase, RARCH_REPLAY_SLOT_PLUS, MENU_ENUM_LABEL_VALUE_INPUT_META_REPLAY_SLOT_PLUS),
DECLARE_META_BIND(2, replay_slot_decrease, RARCH_REPLAY_SLOT_MINUS, MENU_ENUM_LABEL_VALUE_INPUT_META_REPLAY_SLOT_MINUS),
diff --git a/input/bsv/bsvmovie.c b/input/bsv/bsvmovie.c
index 6add8d69ff84..1cde9e47ba5e 100644
--- a/input/bsv/bsvmovie.c
+++ b/input/bsv/bsvmovie.c
@@ -53,10 +53,84 @@ void bsv_movie_free(bsv_movie_t*);
#ifdef HAVE_STATESTREAM
int64_t bsv_movie_write_deduped_state(bsv_movie_t *movie, uint8_t *state, size_t state_size, uint8_t *output, size_t output_capacity);
-bool bsv_movie_read_deduped_state(bsv_movie_t *movie,
- uint8_t *encoded, size_t encoded_size, bool output);
+bool bsv_movie_read_deduped_state(bsv_movie_t *movie, uint8_t *encoded, size_t encoded_size);
#endif
+bool bsv_movie_skip_to_next_checkpoint_impl(bsv_movie_t *movie);
+bool bsv_movie_skip_to_prev_checkpoint_impl(bsv_movie_t *movie);
+bool bsv_movie_seek_to_pos_impl(bsv_movie_t *movie, int64_t pos);
+
+bool bsv_movie_reset_playback(bsv_movie_t *handle)
+{
+ uint32_t state_size = 0;
+ uint32_t header[REPLAY_HEADER_LEN] = {0};
+ uint32_t vsn;
+ if (!handle)
+ return false;
+ vsn = handle->version;
+ intfstream_rewind(handle->file);
+ if (intfstream_read(handle->file, header, REPLAY_HEADER_LEN_BYTES) < REPLAY_HEADER_LEN_BYTES)
+ return false;
+ handle->frame_counter = 0;
+ handle->cur_save_valid = false;
+
+ state_size = swap_if_big32(header[REPLAY_HEADER_STATE_SIZE_INDEX]);
+ if (state_size && vsn <= 1)
+ {
+ size_t info_size;
+ retro_ctx_serialize_info_t serial_info;
+ uint8_t *buf = (uint8_t*)malloc(state_size);
+
+ if (!buf)
+ return false;
+
+ /* The header used to be six ints long */
+ intfstream_seek(handle->file, REPLAY_HEADER_V0V1_LEN_BYTES, SEEK_SET);
+
+ if (intfstream_read(handle->file, buf, state_size) != state_size)
+ {
+ RARCH_ERR("[Replay] %s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
+ return false;
+ }
+ info_size = core_serialize_size();
+ /* For cores like dosbox, the reported size is not always
+ correct. So we just give a warning if they don't match up. */
+ serial_info.data_const = buf;
+ serial_info.size = state_size;
+ core_unserialize(&serial_info);
+ free(buf);
+ if (info_size != state_size)
+ RARCH_WARN("[Replay] %s\n",
+ msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
+ }
+ else if (vsn >= 2)
+ {
+ uint8_t compression, encoding;
+#ifdef HAVE_STATESTREAM
+ uint32_t commit_settings = header[REPLAY_HEADER_CHECKPOINT_CONFIG_INDEX];
+ uint32_t superblock_size = swap_if_big32(header[REPLAY_HEADER_SUPERBLOCK_SIZE_INDEX]);
+ uint32_t block_size = swap_if_big32(header[REPLAY_HEADER_BLOCK_SIZE_INDEX]);
+ handle->commit_interval = commit_settings >> 24;
+ handle->commit_threshold = (commit_settings >> 16) & 0x000000FF;
+ handle->checkpoint_compression = (commit_settings >> 8) & 0x000000FF;
+ if (handle->superblocks)
+ uint32s_index_free(handle->superblocks);
+ handle->superblocks = uint32s_index_new(superblock_size,handle->commit_interval,handle->commit_threshold);
+ if (handle->blocks)
+ uint32s_index_free(handle->blocks);
+ handle->blocks = uint32s_index_new(block_size/4,handle->commit_interval,handle->commit_threshold);
+#endif
+ if (intfstream_read(handle->file, &(compression), sizeof(uint8_t)) != sizeof(uint8_t) ||
+ intfstream_read(handle->file, &(encoding), sizeof(uint8_t)) != sizeof(uint8_t))
+ return false;
+ if (!bsv_movie_load_checkpoint(handle, compression, encoding, REPLAY_CPBEHAVIOR_DESERIALIZE))
+ return false;
+ }
+ if(vsn == 0)
+ return true;
+ return bsv_movie_read_next_events(handle, REPLAY_CPBEHAVIOR_DESERIALIZE, true);
+}
+
bool bsv_movie_reset_recording(bsv_movie_t *handle)
{
size_t state_size, state_size_;
@@ -65,14 +139,16 @@ bool bsv_movie_reset_recording(bsv_movie_t *handle)
uint8_t encoding = REPLAY_CHECKPOINT2_ENCODING_STATESTREAM;
/* If recording, we simply reset
* the starting point. Nice and easy. */
- uint32s_index_clear(handle->superblocks);
- uint32s_index_clear(handle->blocks);
+ if (handle->superblocks)
+ uint32s_index_clear(handle->superblocks);
+ if (handle->blocks)
+ uint32s_index_clear(handle->blocks);
#else
uint8_t encoding = REPLAY_CHECKPOINT2_ENCODING_RAW;
#endif
+ handle->cur_save_valid = false;
intfstream_seek(handle->file, REPLAY_HEADER_LEN_BYTES, SEEK_SET);
- intfstream_truncate(handle->file, REPLAY_HEADER_LEN_BYTES);
intfstream_write(handle->file, &compression, 1);
intfstream_write(handle->file, &encoding, 1);
@@ -132,13 +208,15 @@ void bsv_movie_frame_rewind()
intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
/* clear incremental checkpoint table data. We do this both on recording and playback for simplicity. */
#ifdef HAVE_STATESTREAM
- uint32s_index_remove_after(handle->superblocks, 0);
- uint32s_index_remove_after(handle->blocks, 0);
+ if (handle->superblocks)
+ uint32s_index_remove_after(handle->superblocks, 0);
+ if (handle->blocks)
+ uint32s_index_remove_after(handle->blocks, 0);
#endif
if (recording)
intfstream_truncate(handle->file, (int)handle->min_file_pos);
else
- bsv_movie_read_next_events(handle, false);
+ bsv_movie_read_next_events(handle, REPLAY_CPBEHAVIOR_DESERIALIZE, true);
}
else
{
@@ -154,15 +232,17 @@ void bsv_movie_frame_rewind()
else
handle->frame_counter = 0;
#ifdef HAVE_STATESTREAM
- uint32s_index_remove_after(handle->superblocks, handle->frame_counter);
- uint32s_index_remove_after(handle->blocks, handle->frame_counter);
+ if (handle->superblocks)
+ uint32s_index_remove_after(handle->superblocks, handle->frame_counter);
+ if (handle->blocks)
+ uint32s_index_remove_after(handle->blocks, handle->frame_counter);
#endif
RARCH_LOG("[REPLAY] rewound to %d\n", handle->frame_counter);
intfstream_seek(handle->file, (int)handle->frame_pos[handle->frame_counter & handle->frame_mask], SEEK_SET);
if (recording)
- intfstream_truncate(handle->file, (int)handle->frame_pos[handle->frame_counter & handle->frame_mask]);
+ intfstream_truncate(handle->file, intfstream_tell(handle->file));
else
- bsv_movie_read_next_events(handle, false);
+ bsv_movie_read_next_events(handle, REPLAY_CPBEHAVIOR_DESERIALIZE, true);
}
if (intfstream_tell(handle->file) <= (long)handle->min_file_pos)
@@ -170,17 +250,11 @@ void bsv_movie_frame_rewind()
RARCH_LOG("[Replay] rewound past beginning\n");
/* We rewound past the beginning. */
if (handle->playback)
- {
- intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
-#ifdef HAVE_STATESTREAM
- uint32s_index_remove_after(handle->superblocks, 0);
- uint32s_index_remove_after(handle->blocks, 0);
-#endif
- bsv_movie_read_next_events(handle, false);
- }
+ bsv_movie_reset_playback(handle);
else
{
bsv_movie_reset_recording(handle);
+ intfstream_truncate(handle->file, intfstream_tell(handle->file));
}
}
}
@@ -248,14 +322,13 @@ void bsv_movie_finish_rewind(input_driver_state_t *input_st)
handle->did_rewind = false;
}
-bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t encoding, bool just_update_structures)
+bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t encoding, replay_checkpoint_behavior checkpoint_behavior)
{
input_driver_state_t *input_st = input_state_get_ptr();
uint32_t compressed_encoded_size, encoded_size, size;
uint8_t *compressed_data = NULL, *encoded_data = NULL;
retro_ctx_serialize_info_t serial_info;
bool ret = true;
-
if (intfstream_read(handle->file, &(size),
sizeof(uint32_t)) != sizeof(uint32_t))
{
@@ -280,7 +353,9 @@ bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t
size = swap_if_big32(size);
encoded_size = swap_if_big32(encoded_size);
compressed_encoded_size = swap_if_big32(compressed_encoded_size);
- if (just_update_structures && encoding == REPLAY_CHECKPOINT2_ENCODING_RAW)
+ if (checkpoint_behavior == REPLAY_CPBEHAVIOR_SKIP ||
+ ((checkpoint_behavior == REPLAY_CPBEHAVIOR_UPDATE) &&
+ encoding == REPLAY_CHECKPOINT2_ENCODING_RAW))
{
intfstream_seek(handle->file, compressed_encoded_size, SEEK_CUR);
goto exit;
@@ -370,7 +445,7 @@ bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t
break;
#ifdef HAVE_STATESTREAM
case REPLAY_CHECKPOINT2_ENCODING_STATESTREAM:
- if(!bsv_movie_read_deduped_state(handle, encoded_data, encoded_size, !just_update_structures))
+ if(!bsv_movie_read_deduped_state(handle, encoded_data, encoded_size))
{
RARCH_ERR("[STATESTREAM] Couldn't load incremental checkpoint");
ret = false;
@@ -383,14 +458,13 @@ bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t
ret = false;
goto exit;
}
- if (just_update_structures)
+ if (checkpoint_behavior != REPLAY_CPBEHAVIOR_DESERIALIZE)
goto exit;
serial_info.data_const = handle->cur_save;
serial_info.size = size;
/* TODO: should this happen at the end of the current frame, or at the beginning before inputs have been polled/etc? FCEUMM and PPSSPP have some jankiness here */
if (!core_unserialize(&serial_info))
{
- abort();
ret = false;
goto exit;
}
@@ -532,7 +606,7 @@ int64_t bsv_movie_write_checkpoint(bsv_movie_t *handle, uint8_t compression, uin
return ret;
}
-bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
+bool bsv_movie_read_next_events(bsv_movie_t *handle, replay_checkpoint_behavior checkpoint_behavior, bool end_movie)
{
input_driver_state_t *input_st = input_state_get_ptr();
/* Skip over backref */
@@ -549,7 +623,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Keyboard replay ran out of keyboard inputs too early\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
}
@@ -558,7 +633,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
RARCH_LOG("[Replay] EOF after buttons\n");
/* Natural(?) EOF */
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
if (handle->version > 0)
@@ -574,7 +650,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Input replay ran out of inputs too early\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
}
@@ -583,7 +660,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
RARCH_LOG("[Replay] EOF after inputs\n");
/* Natural(?) EOF */
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
}
@@ -595,7 +673,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Replay ran out of frames\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
else if (next_frame_type == REPLAY_TOKEN_CHECKPOINT_FRAME)
@@ -606,11 +685,12 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
if (intfstream_read(handle->file, &(size), sizeof(uint64_t)) != sizeof(uint64_t))
{
RARCH_ERR("[Replay] Replay ran out of frames\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
size = swap_if_big64(size);
- if(skip_checkpoints)
+ if(checkpoint_behavior != REPLAY_CPBEHAVIOR_DESERIALIZE)
intfstream_seek(handle->file, size, SEEK_CUR);
else
{
@@ -618,7 +698,8 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
if (intfstream_read(handle->file, state, size) != (int64_t)size)
{
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
free(state);
return false;
}
@@ -627,7 +708,10 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
if (!core_unserialize(&serial_info))
{
RARCH_ERR("[Replay] Failed to load movie checkpoint, failing\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ free(state);
+ return false;
}
free(state);
}
@@ -640,31 +724,39 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints)
{
/* Unexpected EOF */
RARCH_ERR("[Replay] Replay checkpoint truncated.\n");
- input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
+ if (end_movie)
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return false;
}
- if (!bsv_movie_load_checkpoint(handle, compression, encoding, skip_checkpoints))
+ if (!bsv_movie_load_checkpoint(handle, compression, encoding, checkpoint_behavior))
RARCH_WARN("[Replay] Failed to load movie checkpoint\n");
}
}
return true;
}
-void bsv_movie_scan_from_start(input_driver_state_t *input_st, int32_t len)
+void bsv_movie_scan_to(bsv_movie_t *movie, int64_t pos)
{
- bsv_movie_t *movie = input_st->bsv_movie_state_handle;
- if (movie->version == 0)
+ if (!movie || movie->version == 0)
return; /* Old movies don't store enough information to fixup the frame counters. */
- intfstream_seek(movie->file, movie->min_file_pos, SEEK_SET);
- movie->frame_counter = 0;
- movie->frame_pos[0] = intfstream_tell(movie->file);
- while(intfstream_tell(movie->file) < len && bsv_movie_read_next_events(movie, true))
+ while(intfstream_tell(movie->file) < pos && bsv_movie_read_next_events(movie, REPLAY_CPBEHAVIOR_UPDATE, false))
{
movie->frame_counter += 1;
movie->frame_pos[movie->frame_counter & movie->frame_mask] = intfstream_tell(movie->file);
}
}
+void bsv_movie_scan_from_start(bsv_movie_t *movie, int32_t len)
+{
+ if (!movie || movie->version == 0)
+ return; /* Old movies don't store enough information to fixup the frame counters. */
+ intfstream_seek(movie->file, movie->min_file_pos, SEEK_SET);
+ movie->frame_counter = 0;
+ movie->frame_pos[0] = intfstream_tell(movie->file);
+ movie->cur_save_valid = false;
+ bsv_movie_scan_to(movie, len);
+}
+
void bsv_movie_next_frame(input_driver_state_t *input_st)
{
unsigned checkpoint_interval = config_get_ptr()->uints.replay_checkpoint_interval;
@@ -689,7 +781,7 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
return;
#endif
- if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING)
+ if (!handle->playback && !(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_SEEKING))
{
int i;
uint16_t evt_count = swap_if_big16(handle->input_event_count);
@@ -713,9 +805,10 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
handle->input_event_count = 0;
/* Maybe record checkpoint */
- if ( (checkpoint_interval != 0)
- && (handle->frame_counter > 0)
- && (handle->frame_counter % (checkpoint_interval*60) == 0))
+ if ((input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_FORCE_CHECKPOINT) ||
+ ((checkpoint_interval != 0)
+ && (handle->frame_counter > 0)
+ && (handle->frame_counter % (checkpoint_interval*60) == 0)))
{
uint8_t frame_tok = REPLAY_TOKEN_CHECKPOINT2_FRAME;
uint8_t compression = handle->checkpoint_compression;
@@ -724,6 +817,7 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
#else
uint8_t encoding = REPLAY_CHECKPOINT2_ENCODING_RAW;
#endif
+ input_st->bsv_movie_state.flags &= ~BSV_FLAG_MOVIE_FORCE_CHECKPOINT;
/* "next frame is a checkpoint" */
intfstream_write(handle->file, (uint8_t *)(&frame_tok), sizeof(uint8_t));
/* compression and encoding schemes */
@@ -741,11 +835,71 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
/* write "next frame is not a checkpoint" */
intfstream_write(handle->file, (uint8_t *)(&frame_tok), sizeof(uint8_t));
}
+ /* To support seeking forwards during a paused replay, we would
+ need to *not* truncate here if we are in the "just paused,
+ running a frame to get the updated image, then will pause
+ again" state. */
+ intfstream_truncate(handle->file, intfstream_tell(handle->file));
+ }
+ else /* either playback or seeking while recording */
+ {
+ bsv_movie_read_next_events(handle, checkpoint_deserialize ? REPLAY_CPBEHAVIOR_DESERIALIZE : REPLAY_CPBEHAVIOR_UPDATE, true);
+ /* clear seeking flag since we did read one frame */
+ input_st->bsv_movie_state.flags &= ~BSV_FLAG_MOVIE_SEEKING;
}
-
- if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
- bsv_movie_read_next_events(handle, !checkpoint_deserialize);
handle->frame_pos[handle->frame_counter & handle->frame_mask] = intfstream_tell(handle->file);
+
+ if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_SEEK_TO_FRAME)
+ {
+ if (bsv_movie_seek_to_pos_impl(handle, input_st->bsv_movie_state.seek_target_pos))
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_FRAME);
+ runloop_msg_queue_push(_msg, strlen(_msg), 10, 15, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_SEEKING;
+ }
+ else
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_FRAME_FAILED);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
+ }
+ input_st->bsv_movie_state.flags &= ~BSV_FLAG_MOVIE_SEEK_TO_FRAME;
+ }
+ else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PREV_CHECKPOINT)
+ {
+ if (bsv_movie_skip_to_prev_checkpoint_impl(handle))
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT);
+ runloop_msg_queue_push(_msg, strlen(_msg), 10, 15, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_SEEKING;
+ }
+ else
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT_FAILED);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
+ }
+ input_st->bsv_movie_state.flags &= ~BSV_FLAG_MOVIE_PREV_CHECKPOINT;
+ }
+ else if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_NEXT_CHECKPOINT)
+ {
+ if (bsv_movie_skip_to_next_checkpoint_impl(handle))
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT);
+ runloop_msg_queue_push(_msg, strlen(_msg), 10, 15, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_SEEKING;
+ }
+ else
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT_FAILED);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
+ }
+ input_st->bsv_movie_state.flags &= ~BSV_FLAG_MOVIE_NEXT_CHECKPOINT;
+ }
}
size_t replay_get_serialize_size(void)
@@ -1028,8 +1182,10 @@ bool replay_set_serialized_data(void* buf)
RARCH_WARN("[Replay] %s.\n", _msg);
}
#ifdef HAVE_STATESTREAM
- uint32s_index_remove_after(handle->superblocks, 0);
- uint32s_index_remove_after(handle->blocks, 0);
+ if (handle->superblocks)
+ uint32s_index_remove_after(handle->superblocks, 0);
+ if (handle->blocks)
+ uint32s_index_remove_after(handle->blocks, 0);
#endif
intfstream_rewind(handle->file);
intfstream_write(handle->file, header, loaded_len);
@@ -1038,20 +1194,22 @@ bool replay_set_serialized_data(void* buf)
/* TODO: in the future, if same_timeline, don't clear
indices above and only scan forward from handle_idx. */
/* TODO use backrefs to help here */
- bsv_movie_scan_from_start(input_st, loaded_len);
+ bsv_movie_scan_from_start(handle, loaded_len);
}
else
{
#ifdef HAVE_STATESTREAM
- uint32s_index_remove_after(handle->superblocks, 0);
- uint32s_index_remove_after(handle->blocks, 0);
+ if (handle->superblocks)
+ uint32s_index_remove_after(handle->superblocks, 0);
+ if (handle->blocks)
+ uint32s_index_remove_after(handle->blocks, 0);
#endif
intfstream_seek(handle->file, loaded_len, SEEK_SET);
/* TODO: in the future, don't clear indices above and only
update frame counter and remove index entries after the
loaded movie's frame counter */
/* TODO use backrefs to help here */
- bsv_movie_scan_from_start(input_st, loaded_len);
+ bsv_movie_scan_from_start(handle, loaded_len);
if (recording)
intfstream_truncate(handle->file, loaded_len);
}
@@ -1256,8 +1414,7 @@ int64_t bsv_movie_write_deduped_state(bsv_movie_t *movie, uint8_t *state, size_t
return encoded_size;
}
-bool bsv_movie_read_deduped_state(bsv_movie_t *movie,
- uint8_t *encoded, size_t encoded_size, bool output)
+bool bsv_movie_read_deduped_state(bsv_movie_t *movie, uint8_t *encoded, size_t encoded_size)
{
static retro_perf_tick_t total_decode_micros = 0;
static retro_perf_tick_t total_decode_count = 0;
@@ -1383,44 +1540,40 @@ bool bsv_movie_read_deduped_state(bsv_movie_t *movie,
goto exit;
}
len = item.val.array.len;
- if (output)
+ if (!movie->superblock_seq)
+ movie->superblock_seq = calloc(len,sizeof(uint32_t));
+ for(i = 0; i < len; i++)
{
- if (!movie->superblock_seq)
- movie->superblock_seq = calloc(len,sizeof(uint32_t));
- for(i = 0; i < len; i++)
+ struct rmsgpack_dom_value inner_item = item.val.array.items[i];
+ /* assert(inner_item.type == RDT_INT); */
+ uint32_t superblock_idx = inner_item.val.int_;
+ uint32_t *superblock;
+ size_t j;
+ /* if this superblock is the same as last time, no need to scan the blocks. */
+ if (movie->cur_save_valid && movie->cur_save && superblock_idx == movie->superblock_seq[i])
{
- struct rmsgpack_dom_value inner_item = item.val.array.items[i];
- /* assert(inner_item.type == RDT_INT); */
- uint32_t superblock_idx = inner_item.val.int_;
- uint32_t *superblock;
- size_t j;
- /* if this superblock is the same as last time, no need to scan the blocks. */
- if (movie->cur_save_valid && movie->cur_save && superblock_idx == movie->superblock_seq[i])
- {
- superblock = uint32s_index_get(movie->superblocks, movie->superblock_seq[i]);
- uint32s_index_bump_count(movie->superblocks, movie->superblock_seq[i]);
- /* We do need to increment all the involved block counts though */
- for (j = 0; j < movie->superblocks->object_size; j++)
- uint32s_index_bump_count(movie->blocks, superblock[j]);
- continue;
- }
- movie->superblock_seq[i] = superblock_idx;
- superblock = uint32s_index_get(movie->superblocks, superblock_idx);
- uint32s_index_bump_count(movie->superblocks, superblock_idx);
- for(j = 0; j < movie->superblocks->object_size; j++)
- {
- uint32_t block_idx = superblock[j];
- size_t block_start = MIN(i*superblock_byte_size+j*block_byte_size, state_size);
- size_t block_end = MIN(block_start+block_byte_size, state_size);
- uint8_t *block;
- /* This (==) can only happen in the last superblock, if it was padded with extra blocks. */
- if(block_end <= block_start) { break; }
- block = (uint8_t *)uint32s_index_get(movie->blocks, block_idx);
- uint32s_index_bump_count(movie->blocks, block_idx);
- memcpy(movie->cur_save+block_start, (uint8_t*)block, block_end-block_start);
- }
+ superblock = uint32s_index_get(movie->superblocks, movie->superblock_seq[i]);
+ uint32s_index_bump_count(movie->superblocks, movie->superblock_seq[i]);
+ /* We do need to increment all the involved block counts though */
+ for (j = 0; j < movie->superblocks->object_size; j++)
+ uint32s_index_bump_count(movie->blocks, superblock[j]);
+ continue;
+ }
+ movie->superblock_seq[i] = superblock_idx;
+ superblock = uint32s_index_get(movie->superblocks, superblock_idx);
+ uint32s_index_bump_count(movie->superblocks, superblock_idx);
+ for(j = 0; j < movie->superblocks->object_size; j++)
+ {
+ uint32_t block_idx = superblock[j];
+ size_t block_start = MIN(i*superblock_byte_size+j*block_byte_size, state_size);
+ size_t block_end = MIN(block_start+block_byte_size, state_size);
+ uint8_t *block;
+ /* This (==) can only happen in the last superblock, if it was padded with extra blocks. */
+ if(block_end <= block_start) { break; }
+ block = (uint8_t *)uint32s_index_get(movie->blocks, block_idx);
+ uint32s_index_bump_count(movie->blocks, block_idx);
+ memcpy(movie->cur_save+block_start, (uint8_t*)block, block_end-block_start);
}
-
}
rmsgpack_dom_value_free(&item);
ret = true;
@@ -1439,10 +1592,225 @@ bool bsv_movie_read_deduped_state(bsv_movie_t *movie,
if(!ret)
{
RARCH_ERR("[STATESTREAM] made it to end without superblock seq\n");
- abort();
+ return false;
}
total_decode_micros += cpu_features_get_time_usec() - start;
RARCH_DBG("[STATESTREAM] Total statestream decodes %d ; net time (secs): %f\n", total_decode_count, (double)total_decode_micros / (1000000.0));
return ret;
}
#endif
+
+bool movie_commit_checkpoint(input_driver_state_t *input_st)
+{
+ if (!input_st->bsv_movie_state_handle)
+ return false;
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_FORCE_CHECKPOINT;
+ return true;
+}
+bool bsv_movie_peek_frame_info(bsv_movie_t *movie, uint8_t *token, uint64_t *len)
+{
+ uint8_t keycount;
+ uint16_t event_count;
+ uint8_t tok;
+ int64_t pos;
+ bool ret = false;
+ if (!movie || movie->version == 0)
+ return ret;
+ pos = intfstream_tell(movie->file);
+ if (movie->version > 1 &&
+ intfstream_seek(movie->file, sizeof(uint32_t), SEEK_CUR) < 0)
+ goto end;
+ if (intfstream_read(movie->file, &keycount, 1) != 1)
+ goto end;
+ if (intfstream_seek(movie->file, sizeof(bsv_key_data_t)*keycount, SEEK_CUR) < 0)
+ goto end;
+ if (intfstream_read(movie->file, &event_count, 2) != 2)
+ goto end;
+ event_count = swap_if_big16(event_count);
+ if (intfstream_seek(movie->file, sizeof(bsv_input_data_t)*event_count, SEEK_CUR) < 0)
+ goto end;
+ if (intfstream_read(movie->file, &tok, 1) != 1)
+ goto end;
+ if (len)
+ {
+ if (tok == REPLAY_TOKEN_CHECKPOINT_FRAME)
+ {
+ uint64_t state_length;
+ if (intfstream_read(movie->file, &(state_length), sizeof(uint64_t)) != sizeof(uint64_t))
+ goto end;
+ state_length = swap_if_big64(state_length);
+ ret = intfstream_seek(movie->file, state_length, SEEK_CUR) >= 0;
+ }
+ else if (tok == REPLAY_TOKEN_CHECKPOINT2_FRAME)
+ {
+ uint32_t state_length;
+ /* skip compression, encoding, uncompressed unencoded size, uncompressed encoded size */
+ if (intfstream_seek(movie->file, 2+2*sizeof(uint32_t), SEEK_CUR) < 0)
+ goto end;
+ /* read compressed encoded size */
+ if (intfstream_read(movie->file, &(state_length), sizeof(uint32_t)) != sizeof(uint32_t))
+ goto end;
+ /* seek past the state data */
+ ret = intfstream_seek(movie->file, state_length, SEEK_CUR) >= 0;
+ }
+ else if (tok == REPLAY_TOKEN_REGULAR_FRAME)
+ {
+ /* we are already at the end of the frame */
+ }
+ else
+ {
+ RARCH_LOG("[Replay] Unrecognized frame token type %c\n", token);
+ goto end;
+ }
+ }
+ ret = true;
+ end:
+ if (ret && token)
+ *token = tok;
+ if (ret && len)
+ *len = intfstream_tell(movie->file) - pos;
+ if (intfstream_seek(movie->file, pos, SEEK_SET) < 0)
+ ret = false;
+ return ret;
+}
+bool movie_find_checkpoint_before(bsv_movie_t *movie, int64_t frame, bool consider_paused,
+ int64_t *cp_pos_out, int64_t *cp_frame_out)
+{
+ /* skip to prev needs to go back at least 60 frames if rewinding when not paused */
+ runloop_state_t *runloop_st = runloop_state_get_ptr();
+ bool paused = !!(runloop_st->flags & RUNLOOP_FLAG_PAUSED) || consider_paused;
+ const int64_t prev_skip_min_distance = 60;
+ int64_t target_frame = frame, cur_frame = 0;
+ bool ret = false;
+ int64_t initial_pos, cp_pos=-1, cp_frame=-1;
+ uint64_t frame_len;
+ uint8_t tok;
+ if (!movie || movie->version == 0)
+ return false;
+ initial_pos = intfstream_tell(movie->file);
+ /* Find the right checkpoint to jump to.
+ In the future, backrefs could be used to make this faster */
+ intfstream_seek(movie->file, movie->min_file_pos, SEEK_SET);
+ while (cur_frame < target_frame && bsv_movie_peek_frame_info(movie, &tok, &frame_len))
+ {
+ if (tok == REPLAY_TOKEN_INVALID)
+ break;
+ if (tok == REPLAY_TOKEN_CHECKPOINT_FRAME || tok == REPLAY_TOKEN_CHECKPOINT2_FRAME)
+ {
+ if (target_frame - cur_frame >= prev_skip_min_distance || paused)
+ {
+ cp_pos = intfstream_tell(movie->file);
+ cp_frame = cur_frame;
+ }
+ }
+ cur_frame += 1;
+ intfstream_seek(movie->file, frame_len, SEEK_CUR);
+ }
+ if (cp_pos_out)
+ *cp_pos_out = cp_pos;
+ if (cp_frame_out)
+ *cp_frame_out = cp_frame;
+ intfstream_seek(movie->file, initial_pos, SEEK_SET);
+ return cp_frame;
+}
+
+bool movie_seek_to_frame(input_driver_state_t *input_st, int64_t frame)
+{
+ if (!input_st->bsv_movie_state_handle)
+ return false;
+#ifdef HAVE_CHEEVOS
+ if (rcheevos_hardcore_active())
+ return false;
+#endif
+ if (!movie_find_checkpoint_before(input_st->bsv_movie_state_handle, frame, true,
+ &input_st->bsv_movie_state.seek_target_pos,
+ &input_st->bsv_movie_state.seek_target_frame))
+ return false;
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_SEEK_TO_FRAME;
+ return true;
+}
+bool bsv_movie_seek_to_pos_impl(bsv_movie_t *movie, int64_t pos)
+{
+ /* TODO:
+ 1. fix under "no previous replay" while recording
+ 2. fix under "some previous replay" while recording
+ */
+ int64_t movie_pos;
+ bool ret;
+ if (!movie || movie->version == 0)
+ return false;
+ movie_pos = intfstream_tell(movie->file);
+ if (pos == movie_pos)
+ return true;
+ /* assume file is at a frame boundary and frame is at a checkpoint boundary. */
+ if (pos < movie_pos)
+ /* TODO: this could be made more efficient with backrefs if we
+ had a way to scan backwards; we wouldn't need to reset to go
+ backwards. */
+ /* It seems strange, but we want `reset_playback` here and not
+ `reset_recording`, even if the movie is in record mode. This
+ is because we don't want to re-serialize the initial state or
+ whatever and act "as if" we just started recording. */
+ bsv_movie_reset_playback(movie);
+ if (pos != movie_pos)
+ bsv_movie_scan_to(movie, pos);
+ return bsv_movie_read_next_events(movie, REPLAY_CPBEHAVIOR_DESERIALIZE, false);
+}
+
+bool movie_skip_to_next_checkpoint(input_driver_state_t *input_st)
+{
+ runloop_state_t *runloop_st = runloop_state_get_ptr();
+ bool paused = !!(runloop_st->flags & RUNLOOP_FLAG_PAUSED);
+ /* Can't skip forward in an unpaused recording replay. */
+ if (!input_st->bsv_movie_state_handle ||
+ (!input_st->bsv_movie_state_handle->playback && !paused) ||
+ input_st->bsv_movie_state_handle->version == 0)
+ return false;
+#ifdef HAVE_CHEEVOS
+ if (rcheevos_hardcore_active())
+ return false;
+#endif
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_NEXT_CHECKPOINT;
+ return true;
+}
+bool bsv_movie_skip_to_next_checkpoint_impl(bsv_movie_t *movie)
+{
+ uint8_t tok = REPLAY_TOKEN_INVALID;
+ uint64_t frame_len;
+ int64_t cp_pos, initial_pos;
+ if (!movie || movie->version == 0)
+ return false;
+ initial_pos = intfstream_tell(movie->file);
+ /* scan forward until peek shows a checkpoint or checkpoint2 */
+ while (bsv_movie_peek_frame_info(movie, &tok, &frame_len) &&
+ (tok != REPLAY_TOKEN_INVALID &&
+ tok != REPLAY_TOKEN_CHECKPOINT_FRAME &&
+ tok != REPLAY_TOKEN_CHECKPOINT2_FRAME))
+ intfstream_seek(movie->file, frame_len, SEEK_CUR);
+ if (tok == REPLAY_TOKEN_INVALID)
+ return false;
+ cp_pos = intfstream_tell(movie->file);
+ intfstream_seek(movie->file, initial_pos, SEEK_SET);
+ return bsv_movie_seek_to_pos_impl(movie, cp_pos);
+}
+bool movie_skip_to_prev_checkpoint(input_driver_state_t *input_st)
+{
+ if (!input_st->bsv_movie_state_handle ||
+ input_st->bsv_movie_state_handle->version == 0)
+ return false;
+#ifdef HAVE_CHEEVOS
+ if (rcheevos_hardcore_active())
+ return false;
+#endif
+ input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_PREV_CHECKPOINT;
+ return true;
+}
+bool bsv_movie_skip_to_prev_checkpoint_impl(bsv_movie_t *movie)
+{
+ int64_t cp_pos;
+ if (!movie || movie->version == 0)
+ return false;
+ if (!movie_find_checkpoint_before(movie, movie->frame_counter, false, &cp_pos, NULL))
+ return false;
+ return bsv_movie_seek_to_pos_impl(movie, cp_pos);
+}
diff --git a/input/bsv/bsvmovie.h b/input/bsv/bsvmovie.h
index c7dc086beaab..b1fe1c7985eb 100644
--- a/input/bsv/bsvmovie.h
+++ b/input/bsv/bsvmovie.h
@@ -38,7 +38,7 @@ void bsv_movie_push_input_event(bsv_movie_t *movie,
bool bsv_movie_load_checkpoint(bsv_movie_t *movie,
uint8_t compression, uint8_t encoding,
- bool just_update_structures);
+ replay_checkpoint_behavior cpbehavior);
int64_t bsv_movie_write_checkpoint(bsv_movie_t *movie,
uint8_t compression, uint8_t encoding);
diff --git a/input/bsv/uint32s_index.c b/input/bsv/uint32s_index.c
index 2191a1141e93..226cf62b4d6d 100644
--- a/input/bsv/uint32s_index.c
+++ b/input/bsv/uint32s_index.c
@@ -252,7 +252,16 @@ uint32_t *uint32s_index_get(uint32s_index_t *index, uint32_t which)
return NULL;
if (!index->objects[which])
{
+ int i;
RARCH_LOG("[STATESTREAM] accessed garbage collected block %d\n", which);
+ for (i = RBUF_LEN(index->additions); i != 0; i--)
+ {
+ if (which >= index->additions[i].first_index)
+ {
+ RARCH_LOG("[STATESTREAM] originally allocated on frame %ld\n", index->additions[i].frame_counter);
+ break;
+ }
+ }
return NULL;
}
return index->objects[which];
diff --git a/input/input_defines.h b/input/input_defines.h
index ea18b5e11a9e..ff9ec45c0f88 100644
--- a/input/input_defines.h
+++ b/input/input_defines.h
@@ -147,6 +147,9 @@ enum
RARCH_PLAY_REPLAY_KEY,
RARCH_RECORD_REPLAY_KEY,
RARCH_HALT_REPLAY_KEY,
+ RARCH_SAVE_REPLAY_CHECKPOINT_KEY,
+ RARCH_PREV_REPLAY_CHECKPOINT_KEY,
+ RARCH_NEXT_REPLAY_CHECKPOINT_KEY,
RARCH_REPLAY_SLOT_PLUS,
RARCH_REPLAY_SLOT_MINUS,
diff --git a/input/input_driver.h b/input/input_driver.h
index 271f9af14141..b03162995a74 100644
--- a/input/input_driver.h
+++ b/input/input_driver.h
@@ -202,16 +202,23 @@ enum bsv_flags
BSV_FLAG_MOVIE_PLAYBACK = (1 << 2),
BSV_FLAG_MOVIE_RECORDING = (1 << 3),
BSV_FLAG_MOVIE_END = (1 << 4),
- BSV_FLAG_MOVIE_EOF_EXIT = (1 << 5)
+ BSV_FLAG_MOVIE_EOF_EXIT = (1 << 5),
+ BSV_FLAG_MOVIE_FORCE_CHECKPOINT = (1 << 6),
+ BSV_FLAG_MOVIE_PREV_CHECKPOINT = (1 << 7),
+ BSV_FLAG_MOVIE_NEXT_CHECKPOINT = (1 << 8),
+ BSV_FLAG_MOVIE_SEEK_TO_FRAME = (1 << 9),
+ BSV_FLAG_MOVIE_SEEKING = (1 << 10)
};
struct bsv_state
{
- uint8_t flags;
+ uint16_t flags;
/* Movie playback/recording support. */
char movie_auto_path[PATH_MAX_LENGTH];
/* Immediate playback/recording. */
char movie_start_path[PATH_MAX_LENGTH];
+ /* Target frame/position to seek to next iteration. */
+ int64_t seek_target_frame, seek_target_pos;
};
/* These data are always little-endian. */
@@ -278,6 +285,15 @@ struct bsv_movie
};
typedef struct bsv_movie bsv_movie_t;
+
+enum replay_checkpoint_behavior_ {
+ REPLAY_CPBEHAVIOR_SKIP,
+ REPLAY_CPBEHAVIOR_UPDATE,
+ REPLAY_CPBEHAVIOR_DESERIALIZE
+};
+
+typedef enum replay_checkpoint_behavior_ replay_checkpoint_behavior;
+
#endif
/**
@@ -1088,13 +1104,18 @@ void input_overlay_check_mouse_cursor(void);
#ifdef HAVE_BSV_MOVIE
void bsv_movie_frame_rewind(void);
void bsv_movie_next_frame(input_driver_state_t *input_st);
-bool bsv_movie_read_next_events(bsv_movie_t *handle, bool skip_checkpoints);
+bool bsv_movie_read_next_events(bsv_movie_t *handle, replay_checkpoint_behavior checkpoint_behavior, bool end_movie_on_eof);
+bool bsv_movie_reset_playback(bsv_movie_t *handle);
bool bsv_movie_reset_recording(bsv_movie_t *handle);
void bsv_movie_finish_rewind(input_driver_state_t *input_st);
void bsv_movie_deinit(input_driver_state_t *input_st);
void bsv_movie_deinit_full(input_driver_state_t *input_st);
void bsv_movie_enqueue(input_driver_state_t *input_st, bsv_movie_t *state, enum bsv_flags flags);
+bool movie_commit_checkpoint(input_driver_state_t *input_st);
+bool movie_skip_to_prev_checkpoint(input_driver_state_t *input_st);
+bool movie_skip_to_next_checkpoint(input_driver_state_t *input_st);
+bool movie_seek_to_frame(input_driver_state_t *input_st, int64_t frame);
bool movie_start_playback(input_driver_state_t *input_st, char *path);
bool movie_start_record(input_driver_state_t *input_st, char *path);
bool movie_stop_playback(input_driver_state_t *input_st);
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 8036cfa06236..944c41c3ee5d 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -4085,6 +4085,30 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_META_HALT_REPLAY_KEY,
"Stops recording/playback of current replay."
)
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY,
+ "Save Replay Checkpoint"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY,
+ "Commits a checkpoint to the currently playing replay."
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY,
+ "Prev Replay Checkpoint"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY,
+ "Rewinds the replay to the previous automatically or manually saved checkpoint."
+ )
+MSG_HASH(
+ MENU_ENUM_LABEL_VALUE_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY,
+ "Next Replay Checkpoint"
+ )
+MSG_HASH(
+ MENU_ENUM_SUBLABEL_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY,
+ "Fast-forwards the replay to the next automatically or manually saved checkpoint."
+ )
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_META_REPLAY_SLOT_PLUS,
"Next Replay Slot"
@@ -14898,6 +14922,30 @@ MSG_HASH(
MSG_REPLAY_LOAD_STATE_OVERWRITING_REPLAY,
"Wrong timeline; overwriting recording"
)
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT,
+ "Seek Back"
+ )
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT_FAILED,
+ "Seek Back Failed"
+ )
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT,
+ "Seek Forward"
+ )
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT_FAILED,
+ "Seek Forward Failed"
+ )
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_FRAME,
+ "Seek Complete"
+ )
+MSG_HASH(
+ MSG_REPLAY_SEEK_TO_FRAME_FAILED,
+ "Seek Failed"
+ )
MSG_HASH(
MSG_FOUND_SHADER,
"Found shader"
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index b768665dbae1..376bfb6b8ea6 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -447,6 +447,9 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_state_slot_minus, ME
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_play_replay_key, MENU_ENUM_SUBLABEL_INPUT_META_PLAY_REPLAY_KEY)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_record_replay_key, MENU_ENUM_SUBLABEL_INPUT_META_RECORD_REPLAY_KEY)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_halt_replay_key, MENU_ENUM_SUBLABEL_INPUT_META_HALT_REPLAY_KEY)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_save_replay_checkpoint_key, MENU_ENUM_SUBLABEL_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_prev_replay_checkpoint_key, MENU_ENUM_SUBLABEL_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_next_replay_checkpoint_key, MENU_ENUM_SUBLABEL_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_replay_slot_plus, MENU_ENUM_SUBLABEL_INPUT_META_REPLAY_SLOT_PLUS)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_replay_slot_minus, MENU_ENUM_SUBLABEL_INPUT_META_REPLAY_SLOT_MINUS)
@@ -2383,6 +2386,15 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case RARCH_HALT_REPLAY_KEY:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_halt_replay_key);
return 0;
+ case RARCH_SAVE_REPLAY_CHECKPOINT_KEY:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_save_replay_checkpoint_key);
+ return 0;
+ case RARCH_PREV_REPLAY_CHECKPOINT_KEY:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_prev_replay_checkpoint_key);
+ return 0;
+ case RARCH_NEXT_REPLAY_CHECKPOINT_KEY:
+ BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_next_replay_checkpoint_key);
+ return 0;
case RARCH_REPLAY_SLOT_PLUS:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_replay_slot_plus);
return 0;
diff --git a/msg_hash.h b/msg_hash.h
index 8d7c8e04671b..3095f572b4fd 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -487,6 +487,12 @@ enum msg_hash_enums
MSG_FAILED_TO_START_MOVIE_RECORD,
MSG_STATE_SLOT,
MSG_REPLAY_SLOT,
+ MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT,
+ MSG_REPLAY_SEEK_TO_PREV_CHECKPOINT_FAILED,
+ MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT,
+ MSG_REPLAY_SEEK_TO_NEXT_CHECKPOINT_FAILED,
+ MSG_REPLAY_SEEK_TO_FRAME,
+ MSG_REPLAY_SEEK_TO_FRAME_FAILED,
MSG_STARTING_MOVIE_RECORD_TO,
MSG_FAILED_TO_APPLY_SHADER,
MSG_FAILED_TO_APPLY_SHADER_PRESET,
@@ -1138,6 +1144,9 @@ enum msg_hash_enums
MENU_ENUM_LABEL_VALUE_INPUT_META_PLAY_REPLAY_KEY,
MENU_ENUM_LABEL_VALUE_INPUT_META_RECORD_REPLAY_KEY,
MENU_ENUM_LABEL_VALUE_INPUT_META_HALT_REPLAY_KEY,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY,
+ MENU_ENUM_LABEL_VALUE_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY,
MENU_ENUM_LABEL_VALUE_INPUT_META_REPLAY_SLOT_PLUS,
MENU_ENUM_LABEL_VALUE_INPUT_META_REPLAY_SLOT_MINUS,
@@ -1238,6 +1247,9 @@ enum msg_hash_enums
MENU_ENUM_SUBLABEL_INPUT_META_PLAY_REPLAY_KEY,
MENU_ENUM_SUBLABEL_INPUT_META_RECORD_REPLAY_KEY,
MENU_ENUM_SUBLABEL_INPUT_META_HALT_REPLAY_KEY,
+ MENU_ENUM_SUBLABEL_INPUT_META_SAVE_REPLAY_CHECKPOINT_KEY,
+ MENU_ENUM_SUBLABEL_INPUT_META_PREV_REPLAY_CHECKPOINT_KEY,
+ MENU_ENUM_SUBLABEL_INPUT_META_NEXT_REPLAY_CHECKPOINT_KEY,
MENU_ENUM_SUBLABEL_INPUT_META_REPLAY_SLOT_PLUS,
MENU_ENUM_SUBLABEL_INPUT_META_REPLAY_SLOT_MINUS,
diff --git a/retroarch.c b/retroarch.c
index cdad61558f89..53636f0044b5 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -3553,6 +3553,21 @@ bool command_event(enum event_command cmd, void *data)
configuration_set_int(settings, settings->ints.state_slot, new_state_slot);
}
break;
+ case CMD_EVENT_SAVE_REPLAY_CHECKPOINT:
+#ifdef HAVE_BSV_MOVIE
+ movie_commit_checkpoint(input_state_get_ptr());
+#endif
+ break;
+ case CMD_EVENT_PREV_REPLAY_CHECKPOINT:
+#ifdef HAVE_BSV_MOVIE
+ movie_skip_to_prev_checkpoint(input_state_get_ptr());
+#endif
+ break;
+ case CMD_EVENT_NEXT_REPLAY_CHECKPOINT:
+#ifdef HAVE_BSV_MOVIE
+ movie_skip_to_next_checkpoint(input_state_get_ptr());
+#endif
+ break;
case CMD_EVENT_REPLAY_DECREMENT:
#ifdef HAVE_BSV_MOVIE
{
diff --git a/runloop.c b/runloop.c
index 8861a06a5548..daabd5bc429c 100644
--- a/runloop.c
+++ b/runloop.c
@@ -19,6 +19,7 @@
* If not, see .
*/
+#include "input/input_driver.h"
#ifdef _WIN32
#ifdef _XBOX
#include
@@ -7009,6 +7010,9 @@ static enum runloop_state_enum runloop_check_state(
HOTKEY_CHECK(RARCH_PLAY_REPLAY_KEY, CMD_EVENT_PLAY_REPLAY, true, NULL);
HOTKEY_CHECK(RARCH_RECORD_REPLAY_KEY, CMD_EVENT_RECORD_REPLAY, true, NULL);
HOTKEY_CHECK(RARCH_HALT_REPLAY_KEY, CMD_EVENT_HALT_REPLAY, true, NULL);
+ HOTKEY_CHECK(RARCH_SAVE_REPLAY_CHECKPOINT_KEY, CMD_EVENT_SAVE_REPLAY_CHECKPOINT, true, NULL);
+ HOTKEY_CHECK(RARCH_PREV_REPLAY_CHECKPOINT_KEY, CMD_EVENT_PREV_REPLAY_CHECKPOINT, true, NULL);
+ HOTKEY_CHECK(RARCH_NEXT_REPLAY_CHECKPOINT_KEY, CMD_EVENT_NEXT_REPLAY_CHECKPOINT, true, NULL);
/* Check Disc Control hotkeys */
HOTKEY_CHECK3(
@@ -7283,6 +7287,17 @@ int runloop_iterate(void)
#ifdef HAVE_NETWORKING
/* FIXME: This is an ugly way to tell Netplay this... */
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
+#endif
+#ifdef HAVE_BSV_MOVIE
+ if (input_st->bsv_movie_state.flags &
+ (BSV_FLAG_MOVIE_FORCE_CHECKPOINT |
+ BSV_FLAG_MOVIE_PREV_CHECKPOINT |
+ BSV_FLAG_MOVIE_NEXT_CHECKPOINT |
+ BSV_FLAG_MOVIE_SEEK_TO_FRAME))
+ {
+ runloop_st->flags &= ~RUNLOOP_FLAG_PAUSED;
+ runloop_st->run_frames_and_pause = 2;
+ }
#endif
video_driver_cached_frame();
goto end;
diff --git a/tasks/task_movie.c b/tasks/task_movie.c
index dd8cc923d616..6e6861459ef2 100644
--- a/tasks/task_movie.c
+++ b/tasks/task_movie.c
@@ -73,11 +73,8 @@ static bool bsv_movie_init_playback(bsv_movie_t *handle, const char *path)
int64_t *identifier_loc;
uint32_t state_size = 0;
uint32_t header[REPLAY_HEADER_LEN] = {0};
+ uint64_t header_size = REPLAY_HEADER_LEN_BYTES;
uint32_t vsn = 0;
-#ifdef HAVE_STATESTREAM
- uint32_t superblock_size = DEFAULT_SUPERBLOCK_SIZE,
- block_size = DEFAULT_BLOCK_SIZE/4;
-#endif
intfstream_t *file = intfstream_open_file(path,
RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
@@ -103,67 +100,16 @@ static bool bsv_movie_init_playback(bsv_movie_t *handle, const char *path)
RARCH_ERR("[Replay] %s : vsn %d vs %d\n", msg_hash_to_str(MSG_MOVIE_FILE_IS_NOT_A_VALID_REPLAY_FILE), vsn, REPLAY_FORMAT_VERSION);
return false;
}
+ if (vsn < 2)
+ header_size = REPLAY_HEADER_V0V1_LEN_BYTES;
handle->version = vsn;
state_size = swap_if_big32(header[REPLAY_HEADER_STATE_SIZE_INDEX]);
identifier_loc = (int64_t *)(header+REPLAY_HEADER_IDENTIFIER_INDEX);
handle->identifier = swap_if_big64(*identifier_loc);
- handle->min_file_pos = sizeof(header) + state_size;
- if (state_size && vsn <= 1)
- {
- size_t info_size;
- retro_ctx_serialize_info_t serial_info;
- uint8_t *buf = (uint8_t*)malloc(state_size);
-
- if (!buf)
- return false;
-
- /* The header used to be six ints long */
- intfstream_seek(handle->file, REPLAY_HEADER_V0V1_LEN_BYTES, SEEK_SET);
-
- if (intfstream_read(handle->file, buf, state_size) != state_size)
- {
- RARCH_ERR("[Replay] %s\n", msg_hash_to_str(MSG_COULD_NOT_READ_STATE_FROM_MOVIE));
- return false;
- }
- info_size = core_serialize_size();
- /* For cores like dosbox, the reported size is not always
- correct. So we just give a warning if they don't match up. */
- serial_info.data_const = buf;
- serial_info.size = state_size;
- core_unserialize(&serial_info);
- free(buf);
- if (info_size != state_size)
- RARCH_WARN("[Replay] %s\n",
- msg_hash_to_str(MSG_MOVIE_FORMAT_DIFFERENT_SERIALIZER_VERSION));
- }
- else if (vsn >= 2)
- {
- uint8_t compression, encoding;
-#ifdef HAVE_STATESTREAM
- uint32_t commit_settings = header[REPLAY_HEADER_CHECKPOINT_CONFIG_INDEX];
- superblock_size = swap_if_big32(header[REPLAY_HEADER_SUPERBLOCK_SIZE_INDEX]);
- block_size = swap_if_big32(header[REPLAY_HEADER_BLOCK_SIZE_INDEX]);
- handle->commit_interval = commit_settings >> 24;
- handle->commit_threshold = (commit_settings >> 16) & 0x000000FF;
- handle->checkpoint_compression = (commit_settings >> 8) & 0x000000FF;
- handle->superblocks = uint32s_index_new(superblock_size,handle->commit_interval,handle->commit_threshold);
- handle->blocks = uint32s_index_new(block_size/4,handle->commit_interval,handle->commit_threshold);
-#endif
-
- if (intfstream_read(handle->file, &(compression), sizeof(uint8_t)) != sizeof(uint8_t) ||
- intfstream_read(handle->file, &(encoding), sizeof(uint8_t)) != sizeof(uint8_t))
- return false;
- if (!bsv_movie_load_checkpoint(handle, compression, encoding, false))
- return false;
- }
-
- if(vsn > 0)
- bsv_movie_read_next_events(handle, false);
-
-
- return true;
+ handle->min_file_pos = header_size + state_size;
+ return bsv_movie_reset_playback(handle);
}
static bool bsv_movie_init_record(
@@ -277,6 +223,14 @@ static bsv_movie_t *bsv_movie_init_internal(const char *path, enum rarch_movie_t
if (!handle)
return NULL;
+ /* Just pick something really large
+ * ~1 million frames rewind should do the trick. */
+ if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
+ goto error;
+
+ handle->frame_pos = frame_pos;
+ handle->frame_mask = (1 << 20) - 1;
+
if (type == RARCH_MOVIE_PLAYBACK)
{
if (!bsv_movie_init_playback(handle, path))
@@ -285,14 +239,7 @@ static bsv_movie_t *bsv_movie_init_internal(const char *path, enum rarch_movie_t
else if (!bsv_movie_init_record(handle, path))
goto error;
- /* Just pick something really large
- * ~1 million frames rewind should do the trick. */
- if (!(frame_pos = (size_t*)calloc((1 << 20), sizeof(size_t))))
- goto error;
-
- handle->frame_pos = frame_pos;
handle->frame_pos[0] = handle->min_file_pos;
- handle->frame_mask = (1 << 20) - 1;
return handle;