Skip to content

Commit e416cfb

Browse files
authored
Fix an off-by-one error in replay checkpoint loading. (#18261)
The issue is that a replay frame read on core frame K stores the inputs for frame K along with the core state as of *the end of frame K*, in other words the beginning of frame K+1. To fix it, and to make sure existing replays play back properly, the `core_unserialize` call in bsvmovie.c has been deferred to the following invocation of `bsv_movie_read_next_events`, so that frame K+1 is deserialized just before reading the inputs for frame K+1. Some refactoring has also been done to simplify the number and timing of calls to bsv_movie functions from runloop.c.
1 parent 9c3966e commit e416cfb

3 files changed

Lines changed: 52 additions & 51 deletions

File tree

input/bsv/bsvmovie.c

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@ void bsv_movie_frame_rewind()
237237
if (handle->blocks)
238238
uint32s_index_remove_after(handle->blocks, handle->frame_counter);
239239
#endif
240-
RARCH_LOG("[REPLAY] rewound to %d\n", handle->frame_counter);
241240
intfstream_seek(handle->file, (int)handle->frame_pos[handle->frame_counter & handle->frame_mask], SEEK_SET);
242241
if (recording)
243242
intfstream_truncate(handle->file, intfstream_tell(handle->file));
@@ -312,16 +311,6 @@ bool bsv_movie_handle_read_input_event(bsv_movie_t *movie,
312311
return false;
313312
}
314313

315-
void bsv_movie_finish_rewind(input_driver_state_t *input_st)
316-
{
317-
bsv_movie_t *handle = input_st->bsv_movie_state_handle;
318-
if (!handle)
319-
return;
320-
handle->frame_counter += 1;
321-
handle->first_rewind = !handle->did_rewind;
322-
handle->did_rewind = false;
323-
}
324-
325314
bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t encoding, replay_checkpoint_behavior checkpoint_behavior)
326315
{
327316
input_driver_state_t *input_st = input_state_get_ptr();
@@ -460,14 +449,7 @@ bool bsv_movie_load_checkpoint(bsv_movie_t *handle, uint8_t compression, uint8_t
460449
}
461450
if (checkpoint_behavior != REPLAY_CPBEHAVIOR_DESERIALIZE)
462451
goto exit;
463-
serial_info.data_const = handle->cur_save;
464-
serial_info.size = size;
465-
/* 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 */
466-
if (!core_unserialize(&serial_info))
467-
{
468-
ret = false;
469-
goto exit;
470-
}
452+
handle->checkpoint_ready = true;
471453
exit:
472454
handle->cur_save_size = size;
473455
handle->last_save_size = handle->cur_save_size;
@@ -609,6 +591,20 @@ int64_t bsv_movie_write_checkpoint(bsv_movie_t *handle, uint8_t compression, uin
609591
bool bsv_movie_read_next_events(bsv_movie_t *handle, replay_checkpoint_behavior checkpoint_behavior, bool end_movie)
610592
{
611593
input_driver_state_t *input_st = input_state_get_ptr();
594+
if (handle->checkpoint_ready)
595+
{
596+
retro_ctx_serialize_info_t serial_info;
597+
handle->checkpoint_ready = false;
598+
serial_info.data_const = handle->cur_save;
599+
serial_info.size = handle->cur_save_size;
600+
if (!core_unserialize(&serial_info))
601+
{
602+
RARCH_ERR("[Replay] Failed to deserialize checkpoint\n");
603+
if (end_movie)
604+
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
605+
return false;
606+
}
607+
}
612608
/* Skip over backref */
613609
if (handle->version > 1)
614610
intfstream_seek(handle->file, sizeof(uint32_t), SEEK_CUR);
@@ -694,26 +690,24 @@ bool bsv_movie_read_next_events(bsv_movie_t *handle, replay_checkpoint_behavior
694690
intfstream_seek(handle->file, size, SEEK_CUR);
695691
else
696692
{
697-
state = (uint8_t*)malloc(size);
698-
if (intfstream_read(handle->file, state, size) != (int64_t)size)
693+
if (!handle->cur_save || handle->cur_save_size < size)
699694
{
700-
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
701-
if (end_movie)
702-
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
703-
free(state);
704-
return false;
695+
if (handle->cur_save)
696+
free(handle->cur_save);
697+
handle->cur_save = malloc(size);
698+
handle->cur_save_size = size;
705699
}
706-
serial_info.data_const = state;
707-
serial_info.size = size;
708-
if (!core_unserialize(&serial_info))
700+
if (intfstream_read(handle->file, handle->cur_save, size) != (int64_t)size)
709701
{
710-
RARCH_ERR("[Replay] Failed to load movie checkpoint, failing\n");
702+
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
711703
if (end_movie)
712704
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
713-
free(state);
705+
free(handle->cur_save);
706+
handle->cur_save = NULL;
714707
return false;
715708
}
716-
free(state);
709+
handle->cur_save_size = size;
710+
handle->checkpoint_ready = true;
717711
}
718712
}
719713
else if (next_frame_type == REPLAY_TOKEN_CHECKPOINT2_FRAME)
@@ -746,6 +740,18 @@ void bsv_movie_scan_to(bsv_movie_t *movie, int64_t pos)
746740
}
747741
}
748742

743+
void bsv_movie_dequeue_next(input_driver_state_t *input_st)
744+
{
745+
if (input_st->bsv_movie_state_next_handle)
746+
{
747+
if (input_st->bsv_movie_state_handle)
748+
bsv_movie_deinit(input_st);
749+
input_st->bsv_movie_state_handle = input_st->bsv_movie_state_next_handle;
750+
input_st->bsv_movie_state_next_handle = NULL;
751+
}
752+
}
753+
754+
749755
void bsv_movie_scan_from_start(bsv_movie_t *movie, int32_t len)
750756
{
751757
if (!movie || movie->version == 0)
@@ -765,22 +771,21 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
765771
bsv_movie_state_handle to bsv_movie_state_next_handle and clear
766772
next_handle */
767773
bsv_movie_t *handle = input_st->bsv_movie_state_handle;
768-
if (input_st->bsv_movie_state_next_handle)
769-
{
770-
if (handle)
771-
bsv_movie_deinit(input_st);
772-
handle = input_st->bsv_movie_state_next_handle;
773-
input_st->bsv_movie_state_handle = handle;
774-
input_st->bsv_movie_state_next_handle = NULL;
775-
}
776774

777775
if (!handle)
778776
return;
779777
#ifdef HAVE_REWIND
780778
if (state_manager_frame_is_reversed())
779+
{
780+
handle->checkpoint_ready = false;
781781
return;
782+
}
782783
#endif
783784

785+
handle->frame_counter += 1;
786+
handle->first_rewind = !handle->did_rewind;
787+
handle->did_rewind = false;
788+
784789
if (!handle->playback && !(input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_SEEKING))
785790
{
786791
int i;
@@ -1276,7 +1281,6 @@ int16_t bsv_movie_read_state(input_driver_state_t *input_st,
12761281
#endif
12771282
return bsv_result;
12781283
}
1279-
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
12801284
return 0;
12811285
}
12821286

input/input_driver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ struct bsv_movie
267267
bool playback;
268268
bool first_rewind;
269269
bool did_rewind;
270+
bool checkpoint_ready;
270271

271272
#ifdef HAVE_STATESTREAM
272273
/* Block index and superblock index for incremental checkpoints */
@@ -1112,6 +1113,7 @@ void bsv_movie_finish_rewind(input_driver_state_t *input_st);
11121113
void bsv_movie_deinit(input_driver_state_t *input_st);
11131114
void bsv_movie_deinit_full(input_driver_state_t *input_st);
11141115
void bsv_movie_enqueue(input_driver_state_t *input_st, bsv_movie_t *state, enum bsv_flags flags);
1116+
void bsv_movie_dequeue_next(input_driver_state_t *input_st);
11151117

11161118
bool movie_commit_checkpoint(input_driver_state_t *input_st);
11171119
bool movie_skip_to_prev_checkpoint(input_driver_state_t *input_st);

runloop.c

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7202,6 +7202,10 @@ int runloop_iterate(void)
72027202
}
72037203
#endif
72047204

7205+
#ifdef HAVE_BSV_MOVIE
7206+
bsv_movie_dequeue_next(input_st);
7207+
#endif
7208+
72057209
if (runloop_st->frame_time.callback)
72067210
{
72077211
/* Updates frame timing if frame timing callback is in use by the core.
@@ -7354,10 +7358,6 @@ int runloop_iterate(void)
73547358
autosave_lock();
73557359
#endif
73567360

7357-
#ifdef HAVE_BSV_MOVIE
7358-
bsv_movie_next_frame(input_st);
7359-
#endif
7360-
73617361
if ( settings->bools.camera_allow
73627362
&& camera_st->cb.caps
73637363
&& camera_st->driver
@@ -7413,12 +7413,7 @@ int runloop_iterate(void)
74137413
presence_update(PRESENCE_GAME);
74147414
#endif
74157415
#ifdef HAVE_BSV_MOVIE
7416-
bsv_movie_finish_rewind(input_st);
7417-
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)
7418-
{
7419-
movie_stop_playback(input_st);
7420-
command_event(CMD_EVENT_PAUSE, NULL);
7421-
}
7416+
bsv_movie_next_frame(input_st);
74227417
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_END)
74237418
{
74247419
movie_stop_playback(input_st);

0 commit comments

Comments
 (0)