diff --git a/input/input_driver.c b/input/input_driver.c
index 49a77e0746..ef4a8df532 100644
--- a/input/input_driver.c
+++ b/input/input_driver.c
@@ -18,6 +18,9 @@
* with RetroArch. If not, see .
**/
+#include "libretro.h"
+#include "queues/message_queue.h"
+#include "streams/interface_stream.h"
#define _USE_MATH_DEFINES
#include
#include
@@ -6461,7 +6464,7 @@ size_t replay_get_serialize_size(void)
{
input_driver_state_t *input_st = &input_driver_st;
if (input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_RECORDING | BSV_FLAG_MOVIE_PLAYBACK))
- return sizeof(int32_t)+intfstream_tell(input_st->bsv_movie_state_handle->file);
+ return sizeof(uint32_t)+intfstream_tell(input_st->bsv_movie_state_handle->file);
return 0;
}
@@ -6472,18 +6475,14 @@ bool replay_get_serialized_data(void* buffer)
if (input_st->bsv_movie_state.flags & (BSV_FLAG_MOVIE_RECORDING | BSV_FLAG_MOVIE_PLAYBACK))
{
- int64_t file_end = intfstream_tell(handle->file);
+ int32_t file_end = (uint32_t)intfstream_tell(handle->file);
int64_t read_amt = 0;
- long file_end_lil = swap_if_big32(file_end);
- uint8_t *file_end_bytes = (uint8_t *)(&file_end_lil);
- uint8_t *buf = buffer;
- buf[0] = file_end_bytes[0];
- buf[1] = file_end_bytes[1];
- buf[2] = file_end_bytes[2];
- buf[3] = file_end_bytes[3];
- buf += 4;
+ int32_t file_end_ = swap_if_big32(file_end);
+ uint8_t *buf;
+ ((uint32_t *)buffer)[0] = file_end_;
+ buf = ((uint8_t *)buffer) + sizeof(uint32_t);
intfstream_rewind(handle->file);
- read_amt = intfstream_read(handle->file, (void *)buf, file_end);
+ read_amt = intfstream_read(handle->file, buf, file_end);
if (read_amt != file_end)
RARCH_ERR("[Replay] Failed to write correct number of replay bytes into state file: %d / %d.\n",
read_amt, file_end);
@@ -6491,13 +6490,164 @@ bool replay_get_serialized_data(void* buffer)
return true;
}
+bool replay_check_same_timeline(bsv_movie_t *movie, uint8_t *other_movie, int64_t other_len)
+{
+ int64_t check_limit = MIN(other_len, intfstream_tell(movie->file));
+ intfstream_t *check_stream = intfstream_open_memory(other_movie, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE, other_len);
+ bool ret = true;
+ int64_t check_cap = MAX(128 << 10, MAX(128*sizeof(bsv_key_data_t), 512*sizeof(bsv_input_data_t)));
+ uint8_t *buf1 = calloc(check_cap,1), *buf2 = calloc(check_cap,1);
+ size_t movie_pos = intfstream_tell(movie->file);
+ uint8_t keycount1, keycount2, frametok1, frametok2;
+ uint16_t btncount1, btncount2;
+ uint64_t size1, size2;
+ intfstream_rewind(movie->file);
+ intfstream_read(movie->file, buf1, 6*sizeof(uint32_t));
+ intfstream_read(check_stream, buf2, 6*sizeof(uint32_t));
+ if (memcmp(buf1, buf2, 6*sizeof(uint32_t)) != 0)
+ {
+ RARCH_ERR("[Replay] Headers of two movies differ, not same timeline\n");
+ ret = false;
+ goto exit;
+ }
+ intfstream_seek(movie->file, movie->min_file_pos, SEEK_SET);
+ /* assumption: both headers have the same state size */
+ intfstream_seek(check_stream, movie->min_file_pos, SEEK_SET);
+ if (movie->version == 0)
+ {
+ int64_t i;
+ /* no choice but to memcmp the whole stream against the other */
+ for (i = 0; ret && i < check_limit; i+=check_cap)
+ {
+ int64_t read_end = MIN(check_limit - i, check_cap);
+ int64_t read1 = intfstream_read(movie->file, buf1, read_end);
+ int64_t read2 = intfstream_read(check_stream, buf2, read_end);
+ if (read1 != read_end || read2 != read_end || memcmp(buf1, buf2, read_end) != 0)
+ {
+ RARCH_ERR("[Replay] One or the other replay checkpoint has different byte values\n");
+ ret = false;
+ goto exit;
+ }
+ }
+ goto exit;
+ }
+ while(intfstream_tell(movie->file) < check_limit && intfstream_tell(check_stream) < check_limit)
+ {
+ if (intfstream_tell(movie->file) < 0 || intfstream_tell(check_stream) < 0)
+ {
+ RARCH_ERR("[Replay] One or the other replay checkpoint has ended prematurely\n");
+ ret = false;
+ goto exit;
+ }
+ if (intfstream_read(movie->file, &keycount1, 1) < 1 ||
+ intfstream_read(check_stream, &keycount2, 1) < 1 ||
+ keycount1 != keycount2)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on key count, %d vs %d\n", keycount1, keycount2);
+ ret = false;
+ goto exit;
+ }
+ if ((uint64_t)intfstream_read(movie->file, buf1, keycount1*sizeof(bsv_key_data_t)) < keycount1*sizeof(bsv_key_data_t) ||
+ (uint64_t)intfstream_read(check_stream, buf2, keycount2*sizeof(bsv_key_data_t)) < keycount2*sizeof(bsv_key_data_t) ||
+ memcmp(buf1, buf2, keycount1*sizeof(bsv_key_data_t)) != 0)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on key data\n");
+ ret = false;
+ goto exit;
+ }
+ if (intfstream_read(movie->file, &btncount1, 2) < 2 ||
+ intfstream_read(check_stream, &btncount2, 2) < 2 ||
+ btncount1 != btncount2)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on input count\n");
+ ret = false;
+ goto exit;
+ }
+ btncount1 = swap_if_big16(btncount1);
+ btncount2 = swap_if_big16(btncount2);
+ if ((uint64_t)intfstream_read(movie->file, buf1, btncount1*sizeof(bsv_input_data_t)) < btncount1*sizeof(bsv_input_data_t) ||
+ (uint64_t)intfstream_read(check_stream, buf2, btncount2*sizeof(bsv_input_data_t)) < btncount2*sizeof(bsv_input_data_t) ||
+ memcmp(buf1, buf2, btncount1*sizeof(bsv_input_data_t)) != 0)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on input data\n");
+ ret = false;
+ goto exit;
+ }
+ if (intfstream_read(movie->file, &frametok1, 1) < 1 ||
+ intfstream_read(check_stream, &frametok2, 1) < 1 ||
+ frametok1 != frametok2)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on frame token\n");
+ ret = false;
+ goto exit;
+ }
+ switch (frametok1)
+ {
+ case REPLAY_TOKEN_INVALID:
+ RARCH_ERR("[Replay] Both replays are somehow invalid\n");
+ ret = false;
+ goto exit;
+ case REPLAY_TOKEN_REGULAR_FRAME:
+ break;
+ case REPLAY_TOKEN_CHECKPOINT_FRAME:
+ if ((uint64_t)intfstream_read(movie->file, &size1, sizeof(uint64_t)) < sizeof(uint64_t) ||
+ (uint64_t)intfstream_read(check_stream, &size2, sizeof(uint64_t)) < sizeof(uint64_t) ||
+ size1 != size2)
+ {
+ RARCH_ERR("[Replay] Replay checkpoints disagree on size or scheme\n");
+ ret = false;
+ goto exit;
+ }
+ size1 = swap_if_big64(size1);
+ intfstream_seek(movie->file, size1, SEEK_CUR);
+ intfstream_seek(check_stream, size1, SEEK_CUR);
+ break;
+ case REPLAY_TOKEN_CHECKPOINT2_FRAME:
+ {
+ uint32_t cpsize1, cpsize2;
+ /* read cp2 header:
+ - one byte compression codec, one byte encoding scheme
+ - 4 byte uncompressed unencoded size, 4 byte uncompressed encoded size
+ - 4 byte compressed, encoded size
+ - the data will follow
+ */
+ if (intfstream_read(movie->file, buf1, 2+sizeof(uint32_t)*3) != 2+sizeof(uint32_t)*3 ||
+ intfstream_read(check_stream, buf2, 2+sizeof(uint32_t)*3) != 2+sizeof(uint32_t)*3 ||
+ memcmp(buf1, buf2, 2+sizeof(uint32_t)*3) != 0
+ )
+ {
+ ret = false;
+ goto exit;
+ }
+ memcpy(&cpsize1, buf1+10, sizeof(uint32_t));
+ memcpy(&cpsize2, buf2+10, sizeof(uint32_t));
+ cpsize1 = swap_if_big32(cpsize1);
+ cpsize2 = swap_if_big32(cpsize2);
+ intfstream_seek(movie->file, cpsize1, SEEK_CUR);
+ intfstream_seek(check_stream, cpsize2, SEEK_CUR);
+ break;
+ }
+ default:
+ RARCH_ERR("[Replay] Unrecognized frame token in both replays\n");
+ ret = false;
+ goto exit;
+ }
+ }
+ exit:
+ free(buf1);
+ free(buf2);
+ intfstream_close(check_stream);
+ intfstream_seek(movie->file, movie_pos, SEEK_SET);
+ return ret;
+}
+
bool replay_set_serialized_data(void* buf)
{
uint8_t *buffer = buf;
input_driver_state_t *input_st = &input_driver_st;
bool playback = (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK) ? true : false;
bool recording = (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING) ? true : false;
-
+ bsv_movie_t *movie = input_st->bsv_movie_state_handle;
/* If there is no current replay, ignore this entirely.
TODO/FIXME: Later, consider loading up the replay
and allow the user to continue it?
@@ -6529,15 +6679,18 @@ bool replay_set_serialized_data(void* buf)
else
{
/* TODO: should factor the next few lines away, magic numbers ahoy */
- uint32_t *header = (uint32_t *)(buffer + sizeof(int32_t));
+ uint32_t *header = (uint32_t *)(buffer + sizeof(uint32_t));
int64_t *ident_spot = (int64_t *)(header + 4);
- int64_t ident = swap_if_big64(*ident_spot);
+ int64_t ident;
+ /* avoid unaligned 8-byte read */
+ memcpy(&ident, ident_spot, sizeof(int64_t));
+ ident = swap_if_big64(ident);
- if (ident == input_st->bsv_movie_state_handle->identifier) /* is compatible? */
+ if (ident == movie->identifier) /* is compatible? */
{
- int32_t loaded_len = swap_if_big32(((int32_t *)buffer)[0]);
- int64_t handle_idx = intfstream_tell(
- input_st->bsv_movie_state_handle->file);
+ int64_t loaded_len = (int64_t)swap_if_big32(((uint32_t *)buffer)[0]);
+ int64_t handle_idx = intfstream_tell(movie->file);
+ bool same_timeline = replay_check_same_timeline(movie, (uint8_t *)header, loaded_len);
/* If the state is part of this replay, go back to that state
and rewind/fast forward the replay.
@@ -6548,19 +6701,39 @@ bool replay_set_serialized_data(void* buf)
This can truncate the current replay if we're in recording mode.
*/
- if (loaded_len > handle_idx)
+ if (playback && loaded_len > handle_idx)
{
- /* TODO: Really, to be very careful, we should be
- checking that the events in the loaded state are the
- same up to handle_idx. Right? */
- intfstream_rewind(input_st->bsv_movie_state_handle->file);
- intfstream_write(input_st->bsv_movie_state_handle->file, buffer+sizeof(int32_t), loaded_len);
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_LOAD_STATE_FAILED_FUTURE_STATE);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
+ RARCH_ERR("[Replay] %s.\n", _msg);
+ return false;
+ }
+ else if (playback && !same_timeline)
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_LOAD_STATE_FAILED_WRONG_TIMELINE);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
+ RARCH_ERR("[Replay] %s.\n", _msg);
+ return false;
+ }
+ else if (recording && (loaded_len > handle_idx || !same_timeline))
+ {
+ if (!same_timeline)
+ {
+ const char *_msg = msg_hash_to_str(MSG_REPLAY_LOAD_STATE_OVERWRITING_REPLAY);
+ runloop_msg_queue_push(_msg, strlen(_msg), 1, 180, true, NULL,
+ MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
+ RARCH_WARN("[Replay] %s.\n", _msg);
+ }
+ intfstream_rewind(movie->file);
+ intfstream_write(movie->file, buffer+sizeof(int32_t), loaded_len);
}
else
{
- intfstream_seek(input_st->bsv_movie_state_handle->file, loaded_len, SEEK_SET);
+ intfstream_seek(movie->file, loaded_len, SEEK_SET);
if (recording)
- intfstream_truncate(input_st->bsv_movie_state_handle->file, loaded_len);
+ intfstream_truncate(movie->file, loaded_len);
}
}
else
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 93a0427348..902912c6f3 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -14829,6 +14829,18 @@ MSG_HASH(
MSG_REPLAY_LOAD_STATE_HALT_INCOMPAT,
"Not compatible with replay"
)
+MSG_HASH(
+ MSG_REPLAY_LOAD_STATE_FAILED_FUTURE_STATE,
+ "Can't load future state during playback"
+ )
+MSG_HASH(
+ MSG_REPLAY_LOAD_STATE_FAILED_WRONG_TIMELINE,
+ "Wrong timeline error during playback"
+ )
+MSG_HASH(
+ MSG_REPLAY_LOAD_STATE_OVERWRITING_REPLAY,
+ "Wrong timeline; overwriting recording"
+ )
MSG_HASH(
MSG_FOUND_SHADER,
"Found shader"
diff --git a/msg_hash.h b/msg_hash.h
index 11a8ade127..1dc3f16b99 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -285,6 +285,9 @@ enum msg_hash_enums
MSG_FOUND_LAST_REPLAY_SLOT,
MSG_REPLAY_LOAD_STATE_HALT_INCOMPAT,
MSG_REPLAY_LOAD_STATE_FAILED_INCOMPAT,
+ MSG_REPLAY_LOAD_STATE_FAILED_FUTURE_STATE,
+ MSG_REPLAY_LOAD_STATE_FAILED_WRONG_TIMELINE,
+ MSG_REPLAY_LOAD_STATE_OVERWRITING_REPLAY,
MSG_RESTORED_OLD_SAVE_STATE,
MSG_NO_STATE_HAS_BEEN_LOADED_YET,
MSG_GOT_CONNECTION_FROM,