Skip to content

Commit

Permalink
replay format extended to support external tools (#17042)
Browse files Browse the repository at this point in the history
- replays now start each frame with the number of key events (8 bit
unsigned int, then key events) and the number of input events (16 bit
unsigned int, then the input events)
- this makes it possible to parse replay files without any core
loaded, and makes replays more portable if cores change their polling
strategies
- external tools can now parse replay files
- old (vsn 0) replays will still play back, but new (vsn 1) replays
will not play on old RA
- replay files grow faster now, with each input poll now taking 8
bytes instead of 2
  • Loading branch information
JoeOsborn authored Oct 4, 2024
1 parent c84962a commit fbf2c70
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 87 deletions.
241 changes: 166 additions & 75 deletions input/input_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -5693,6 +5693,8 @@ void bsv_movie_frame_rewind(void)
intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
if (recording)
intfstream_truncate(handle->file, (int)handle->min_file_pos);
else
bsv_movie_read_next_events(handle);
}
else
{
Expand All @@ -5710,6 +5712,8 @@ void bsv_movie_frame_rewind(void)
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]);
else
bsv_movie_read_next_events(handle);
}

if (intfstream_tell(handle->file) <= (long)handle->min_file_pos)
Expand All @@ -5734,7 +5738,10 @@ void bsv_movie_frame_rewind(void)
intfstream_write(handle->file, handle->state, handle->state_size);
}
else
{
intfstream_seek(handle->file, (int)handle->min_file_pos, SEEK_SET);
bsv_movie_read_next_events(handle);
}
}
}

Expand All @@ -5743,18 +5750,62 @@ static void bsv_movie_handle_clear_key_events(bsv_movie_t *movie)
{
movie->key_event_count = 0;
}
/* Zero out input events when playing back or recording */
static void bsv_movie_handle_clear_input_events(bsv_movie_t *movie)
{
movie->input_event_count = 0;
}

void bsv_movie_handle_push_key_event(bsv_movie_t *movie,
uint8_t down, uint16_t mod, uint32_t code, uint32_t character)
{
bsv_key_data_t data;
data.down = down;
data._padding = 0;
data.mod = swap_if_big16(mod);
data.code = swap_if_big32(code);
data.character = swap_if_big32(character);
movie->key_events[movie->key_event_count] = data;
movie->key_event_count++;
}
void bsv_movie_handle_push_input_event(bsv_movie_t *movie,
uint8_t port, uint8_t dev, uint8_t idx, uint16_t id, int16_t val)
{
bsv_input_data_t data;
data.port = port;
data.device = dev;
data.idx = idx;
data._padding = 0;
data.id = swap_if_big16(id);
data.value = swap_if_big16(val);
movie->input_events[movie->input_event_count] = data;
movie->input_event_count++;
}
bool bsv_movie_handle_read_input_event(bsv_movie_t *movie,
uint8_t port, uint8_t dev, uint8_t idx, uint16_t id, int16_t* val)
{
int i;
/* if movie is old, just read two bytes and hope for the best */
if (movie->version == 0)
{
int read = intfstream_read(movie->file, val, 2);
*val = swap_if_big16(*val);
return read == 2;
}
for (i = 0; i < movie->input_event_count; i++)
{
bsv_input_data_t evt = movie->input_events[i];
if (evt.port == port &&
evt.device == dev &&
evt.idx == idx &&
evt.id == id)
{
*val = swap_if_big16(evt.value);
return true;
}
}
return false;
}

void bsv_movie_finish_rewind(input_driver_state_t *input_st)
{
Expand All @@ -5765,7 +5816,99 @@ void bsv_movie_finish_rewind(input_driver_state_t *input_st)
handle->first_rewind = !handle->did_rewind;
handle->did_rewind = false;
}
void bsv_movie_read_next_events(bsv_movie_t *handle)
{
input_driver_state_t *input_st = input_state_get_ptr();
if (intfstream_read(handle->file, &(handle->key_event_count), 1) == 1)
{
int i;
for (i = 0; i < handle->key_event_count; i++)
{
if (intfstream_read(handle->file, &(handle->key_events[i]), sizeof(bsv_key_data_t)) != sizeof(bsv_key_data_t))
{
/* 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;
return;
}
}
}
else
{
RARCH_LOG("[Replay] EOF after buttons\n");
/* Natural(?) EOF */
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}
if (handle->version > 0)
{
if (intfstream_read(handle->file, &(handle->input_event_count), 2) == 2)
{
int i;
handle->input_event_count = swap_if_big16(handle->input_event_count);
for (i = 0; i < handle->input_event_count; i++)
{
if (intfstream_read(handle->file, &(handle->input_events[i]), sizeof(bsv_input_data_t)) != sizeof(bsv_input_data_t))
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Input replay ran out of inputs too early\n");
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}
}
}
else
{
RARCH_LOG("[Replay] EOF after inputs\n");
/* Natural(?) EOF */
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}
}

{
uint8_t next_frame_type=REPLAY_TOKEN_INVALID;
if (intfstream_read(handle->file, (uint8_t *)(&next_frame_type), sizeof(uint8_t)) != sizeof(uint8_t))
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Replay ran out of frames\n");
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}
else if (next_frame_type == REPLAY_TOKEN_REGULAR_FRAME)
{
/* do nothing */
}
else if (next_frame_type == REPLAY_TOKEN_CHECKPOINT_FRAME)
{
uint64_t size;
uint8_t *st;
retro_ctx_serialize_info_t serial_info;

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;
return;
}

size = swap_if_big64(size);
st = (uint8_t*)malloc(size);
if (intfstream_read(handle->file, st, size) != (int64_t)size)
{
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
free(st);
return;
}

serial_info.data_const = st;
serial_info.size = size;
core_unserialize(&serial_info);
free(st);
}
}
}
void bsv_movie_next_frame(input_driver_state_t *input_st)
{
settings_t *settings = config_get_ptr();
Expand Down Expand Up @@ -5793,11 +5936,17 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_RECORDING)
{
int i;
uint16_t evt_count = swap_if_big16(handle->input_event_count);
/* write key events, frame is over */
intfstream_write(handle->file, &(handle->key_event_count), 1);
for (i = 0; i < handle->key_event_count; i++)
intfstream_write(handle->file, &(handle->key_events[i]), sizeof(bsv_key_data_t));
bsv_movie_handle_clear_key_events(handle);
/* write input events, frame is over */
intfstream_write(handle->file, &evt_count, 2);
for (i = 0; i < handle->input_event_count; i++)
intfstream_write(handle->file, &(handle->input_events[i]), sizeof(bsv_input_data_t));
bsv_movie_handle_clear_input_events(handle);

/* Maybe record checkpoint */
if (checkpoint_interval != 0 && handle->frame_counter > 0 && (handle->frame_counter % (checkpoint_interval*60) == 0))
Expand Down Expand Up @@ -5826,75 +5975,7 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)

if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
{
/* read next key events, a frame happened for sure? but don't apply them yet */
if (handle->key_event_count != 0)
{
RARCH_ERR("[Replay] Keyboard replay reading next frame while some unused keys still in queue\n");
}
if (intfstream_read(handle->file, &(handle->key_event_count), 1) == 1)
{
int i;
for (i = 0; i < handle->key_event_count; i++)
{
if (intfstream_read(handle->file, &(handle->key_events[i]), sizeof(bsv_key_data_t)) != sizeof(bsv_key_data_t))
{
/* 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;
return;
}
}
}
else
{
RARCH_LOG("[Replay] EOF after buttons\n",handle->key_event_count);
/* Natural(?) EOF */
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}

{
uint8_t next_frame_type=REPLAY_TOKEN_INVALID;
if (intfstream_read(handle->file, (uint8_t *)(&next_frame_type), sizeof(uint8_t)) != sizeof(uint8_t))
{
/* Unnatural EOF */
RARCH_ERR("[Replay] Replay ran out of frames\n");
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
return;
}
else if (next_frame_type == REPLAY_TOKEN_REGULAR_FRAME)
{
/* do nothing */
}
else if (next_frame_type == REPLAY_TOKEN_CHECKPOINT_FRAME)
{
uint64_t size;
uint8_t *st;
retro_ctx_serialize_info_t serial_info;

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;
return;
}

size = swap_if_big64(size);
st = (uint8_t*)malloc(size);
if (intfstream_read(handle->file, st, size) != (int64_t)size)
{
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
free(st);
return;
}

serial_info.data_const = st;
serial_info.size = size;
core_unserialize(&serial_info);
free(st);
}
}
bsv_movie_read_next_events(handle);
}
handle->frame_pos[handle->frame_counter & handle->frame_mask] = intfstream_tell(handle->file);
}
Expand Down Expand Up @@ -6524,14 +6605,19 @@ int16_t input_driver_state_wrapper(unsigned port, unsigned device,
if (BSV_MOVIE_IS_PLAYBACK_ON())
{
int16_t bsv_result = 0;
if (intfstream_read(
input_st->bsv_movie_state_handle->file,
&bsv_result, 2) == 2)
bsv_movie_t *movie = input_st->bsv_movie_state_handle;
if (bsv_movie_handle_read_input_event(
movie,
port,
device,
idx,
id,
&bsv_result))
{
#ifdef HAVE_CHEEVOS
rcheevos_pause_hardcore();
#endif
return swap_if_big16(bsv_result);
return bsv_result;
}

input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
Expand All @@ -6552,8 +6638,13 @@ int16_t input_driver_state_wrapper(unsigned port, unsigned device,
/* Save input to BSV record, if enabled */
if (BSV_MOVIE_IS_RECORDING())
{
result = swap_if_big16(result);
intfstream_write(input_st->bsv_movie_state_handle->file, &result, 2);
bsv_movie_handle_push_input_event(
input_st->bsv_movie_state_handle,
port,
device,
idx,
id,
result);
}
#endif

Expand Down
30 changes: 23 additions & 7 deletions input/input_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,30 +184,45 @@ struct bsv_state
struct bsv_key_data
{
uint8_t down;
uint16_t mod;
uint8_t _padding;
uint16_t mod;
uint32_t code;
uint32_t character;
};

typedef struct bsv_key_data bsv_key_data_t;

struct bsv_input_data
{
uint8_t port;
uint8_t device;
uint8_t idx;
uint8_t _padding;
/* little-endian numbers */
uint16_t id;
int16_t value;
};
typedef struct bsv_input_data bsv_input_data_t;

struct bsv_movie
{
intfstream_t *file;
uint8_t *state;
int64_t identifier;
uint32_t version;
size_t min_file_pos;
size_t state_size;

/* A ring buffer keeping track of positions
* in the file for each frame. */
size_t *frame_pos;
int64_t identifier;
size_t frame_mask;
uint64_t frame_counter;
size_t min_file_pos;
size_t state_size;
bsv_key_data_t key_events[NAME_MAX_LENGTH]; /* uint32_t alignment */

/* Staging variables for keyboard events */
/* Staging variables for events */
uint8_t key_event_count;
uint16_t input_event_count;
bsv_key_data_t key_events[128];
bsv_input_data_t input_events[512];

/* Rewind state */
bool playback;
Expand Down Expand Up @@ -1018,6 +1033,7 @@ 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);
void bsv_movie_read_next_events(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);
Expand Down
Loading

0 comments on commit fbf2c70

Please sign in to comment.