replay: introduce REPLAY_STRUCT_VERSION_TS104000_REV1

* Use zstd instead of zlib compression
* Remove plr_focus field
* Add skip_frames field for demos
This commit is contained in:
Andrei Alexeyev 2023-06-13 04:06:30 +02:00
parent e74c3f60ea
commit eede63333a
No known key found for this signature in database
GPG key ID: 72D26128040B9690
8 changed files with 143 additions and 54 deletions

View file

@ -12,7 +12,6 @@
#include "rw_common.h"
#include "player.h"
#include "rwops/rwops_zlib.h"
#include "rwops/rwops_segment.h"
#ifdef REPLAY_LOAD_DEBUG
@ -78,6 +77,7 @@ static bool replay_read_header(Replay *rpy, SDL_RWops *file, int64_t filesize, s
case REPLAY_STRUCT_VERSION_TS103000_REV2:
case REPLAY_STRUCT_VERSION_TS103000_REV3:
case REPLAY_STRUCT_VERSION_TS104000_REV0:
case REPLAY_STRUCT_VERSION_TS104000_REV1:
{
if(taisei_version_read(file, &rpy->game_version) != TAISEI_VERSION_SIZE) {
log_error("%s: Failed to read game version", source);
@ -149,21 +149,35 @@ static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, con
CHECKPROP(stg->diff = SDL_ReadU8(file), u);
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
CHECKPROP(stg->skip_frames = SDL_ReadLE16(file), u);
}
if(version >= REPLAY_STRUCT_VERSION_TS103000_REV0) {
CHECKPROP(stg->plr_points = SDL_ReadLE64(file), zu);
} else {
CHECKPROP(stg->plr_points = SDL_ReadLE32(file), zu);
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
CHECKPROP(stg->plr_total_lives_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_total_bombs_used = SDL_ReadU8(file), u);
}
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
CHECKPROP(stg->plr_continues_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_total_continues_used = SDL_ReadU8(file), u);
}
CHECKPROP(stg->plr_char = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_shot = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_pos_x = SDL_ReadLE16(file), u);
CHECKPROP(stg->plr_pos_y = SDL_ReadLE16(file), u);
CHECKPROP(stg->plr_focus = SDL_ReadU8(file), u); // FIXME remove and bump version
if(version <= REPLAY_STRUCT_VERSION_TS104000_REV0) {
// NOTE: old plr_focus field, ignored
SDL_ReadU8(file);
}
CHECKPROP(stg->plr_power = SDL_ReadLE16(file), u);
CHECKPROP(stg->plr_lives = SDL_ReadU8(file), u);
@ -195,12 +209,16 @@ static bool replay_read_meta(Replay *rpy, SDL_RWops *file, int64_t filesize, con
CHECKPROP(stg->plr_points_final = SDL_ReadLE64(file), zu);
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) {
CHECKPROP(stg->plr_stats_total_lives_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stats_stage_lives_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stats_total_bombs_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stats_stage_bombs_used = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stats_stage_continues_used = SDL_ReadU8(file), u);
if(version == REPLAY_STRUCT_VERSION_TS104000_REV0) {
// NOTE: These fields were always bugged, so we ignore them
uint8_t junk[5];
SDL_RWread(file, junk, sizeof(junk), 1);
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
CHECKPROP(stg->plr_stage_lives_used_final = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stage_bombs_used_final = SDL_ReadU8(file), u);
CHECKPROP(stg->plr_stage_continues_used_final = SDL_ReadU8(file), u);
}
CHECKPROP(stg->num_events = SDL_ReadLE16(file), u);
@ -283,9 +301,9 @@ bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode, const char *
return false;
}
vfile = SDL_RWWrapZlibReader(
vfile = replay_wrap_stream_decompress(
rpy->version,
SDL_RWWrapSegment(file, ofs, rpy->fileoffset, false),
REPLAY_COMPRESSION_CHUNK_SIZE,
true
);
@ -332,7 +350,7 @@ bool replay_read(Replay *rpy, SDL_RWops *file, ReplayReadMode mode, const char *
bool compression = false;
if(rpy->version & REPLAY_VERSION_COMPRESSION_BIT) {
vfile = SDL_RWWrapZlibReader(file, REPLAY_COMPRESSION_CHUNK_SIZE, false);
vfile = replay_wrap_stream_decompress(rpy->version, file, false);
filesize = -1;
compression = true;
}

View file

@ -9,6 +9,8 @@
#include "taisei.h"
#include "rw_common.h"
#include "rwops/rwops_zlib.h"
#include "rwops/rwops_zstd.h"
uint8_t replay_magic_header[REPLAY_MAGIC_HEADER_SIZE] = REPLAY_MAGIC_HEADER;
@ -18,12 +20,16 @@ uint32_t replay_struct_stage_metadata_checksum(ReplayStage *stg, uint16_t versio
cs += stg->stage;
cs += stg->rng_seed;
cs += stg->diff;
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
cs += stg->skip_frames;
}
cs += stg->plr_points;
cs += stg->plr_char;
cs += stg->plr_shot;
cs += stg->plr_pos_x;
cs += stg->plr_pos_y;
cs += stg->plr_focus; // FIXME remove and bump version
cs += stg->plr_power;
cs += stg->plr_lives;
cs += stg->plr_life_fragments;
@ -37,8 +43,13 @@ uint32_t replay_struct_stage_metadata_checksum(ReplayStage *stg, uint16_t versio
cs += stg->num_events;
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
cs += stg->plr_total_lives_used;
cs += stg->plr_total_continues_used;
}
if(version >= REPLAY_STRUCT_VERSION_TS102000_REV1) {
cs += stg->plr_continues_used;
cs += stg->plr_total_continues_used;
cs += stg->flags;
}
@ -54,14 +65,23 @@ uint32_t replay_struct_stage_metadata_checksum(ReplayStage *stg, uint16_t versio
cs += stg->plr_points_final;
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) {
cs += stg->plr_stats_total_lives_used;
cs += stg->plr_stats_stage_lives_used;
cs += stg->plr_stats_total_bombs_used;
cs += stg->plr_stats_stage_bombs_used;
cs += stg->plr_stats_stage_continues_used;
}
log_debug("%08x", cs);
return cs;
}
SDL_RWops *replay_wrap_stream_compress(uint16_t version, SDL_RWops *rw, bool autoclose) {
if((version & ~REPLAY_VERSION_COMPRESSION_BIT) >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
return SDL_RWWrapZstdWriter(rw, 22, autoclose);
} else {
return SDL_RWWrapZlibWriter(
rw, RW_DEFLATE_LEVEL_DEFAULT, REPLAY_COMPRESSION_CHUNK_SIZE, autoclose);
}
}
SDL_RWops *replay_wrap_stream_decompress(uint16_t version, SDL_RWops *rw, bool autoclose) {
if((version & ~REPLAY_VERSION_COMPRESSION_BIT) >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
return SDL_RWWrapZstdReader(rw, autoclose);
} else {
return SDL_RWWrapZlibReader(rw, REPLAY_COMPRESSION_CHUNK_SIZE, autoclose);
}
}

View file

@ -13,4 +13,7 @@
uint32_t replay_struct_stage_metadata_checksum(ReplayStage *stg, uint16_t version);
SDL_RWops *replay_wrap_stream_compress(uint16_t version, SDL_RWops *rw, bool autoclose);
SDL_RWops *replay_wrap_stream_decompress(uint16_t version, SDL_RWops *rw, bool autoclose);
extern uint8_t replay_magic_header[REPLAY_MAGIC_HEADER_SIZE];

View file

@ -29,8 +29,9 @@ ReplayStage *replay_stage_new(Replay *rpy, StageInfo *stage, uint64_t start_time
s->plr_pos_y = floor(cimag(plr->pos));
s->plr_points = plr->points;
s->plr_continues_used = plr->stats.total.continues_used;
// s->plr_focus = plr->focus; FIXME remove and bump version
s->plr_total_lives_used = plr->stats.total.lives_used;
s->plr_total_bombs_used = plr->stats.total.bombs_used;
s->plr_total_continues_used = plr->stats.total.continues_used;
s->plr_char = plr->mode->character->id;
s->plr_shot = plr->mode->shot_mode;
s->plr_lives = plr->lives;
@ -48,10 +49,11 @@ ReplayStage *replay_stage_new(Replay *rpy, StageInfo *stage, uint64_t start_time
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) {
plr->points = stg->plr_points;
plr->stats.total.continues_used = stg->plr_continues_used;
plr->stats.total.lives_used = stg->plr_total_lives_used;
plr->stats.total.bombs_used = stg->plr_total_bombs_used;
plr->stats.total.continues_used = stg->plr_total_continues_used;
plr->mode = plrmode_find(stg->plr_char, stg->plr_shot);
plr->pos = stg->plr_pos_x + I * stg->plr_pos_y;
// plr->focus = stg->plr_focus; FIXME remove and bump version
plr->lives = stg->plr_lives;
plr->life_fragments = stg->plr_life_fragments;
plr->bombs = stg->plr_bombs;
@ -60,12 +62,12 @@ void replay_stage_sync_player_state(ReplayStage *stg, Player *plr) {
plr->graze = stg->plr_graze;
plr->point_item_value = stg->plr_point_item_value;
plr->inputflags = stg->plr_inputflags;
}
plr->stats.total.lives_used = stg->plr_stats_total_lives_used;
plr->stats.stage.lives_used = stg->plr_stats_stage_lives_used;
plr->stats.total.bombs_used = stg->plr_stats_total_bombs_used;
plr->stats.stage.bombs_used = stg->plr_stats_stage_bombs_used;
plr->stats.stage.continues_used = stg->plr_stats_stage_continues_used;
void replay_stage_update_final_stats(ReplayStage *stg, const Stats *stats) {
stg->plr_stage_lives_used_final = stats->stage.lives_used;
stg->plr_stage_bombs_used_final = stats->stage.bombs_used;
stg->plr_stage_continues_used_final = stats->stage.continues_used;
}
void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, uint16_t value) {

View file

@ -29,5 +29,8 @@ void replay_stage_event(ReplayStage *stg, uint32_t frame, uint8_t type, uint16_t
void replay_stage_sync_player_state(ReplayStage *stg, Player *plr)
attr_nonnull_all;
void replay_stage_update_final_stats(ReplayStage *stg, const Stats *stats)
attr_nonnull_all;
void replay_stage_destroy_events(ReplayStage *stg)
attr_nonnull_all;

View file

@ -52,13 +52,17 @@
// Taisei v1.4 revision 0: add statistics for player
#define REPLAY_STRUCT_VERSION_TS104000_REV0 13
// Taisei v1.4 revision 1: switch to zstd compression, remove plr_focus, add skip_frames (for demos), rework/fix player resource usage stats
#define REPLAY_STRUCT_VERSION_TS104000_REV1 14
/* END supported struct versions */
#define REPLAY_VERSION_COMPRESSION_BIT 0x8000
#define REPLAY_COMPRESSION_CHUNK_SIZE 4096
// What struct version to use when saving recorded replays
#define REPLAY_STRUCT_VERSION_WRITE (REPLAY_STRUCT_VERSION_TS104000_REV0 | REPLAY_VERSION_COMPRESSION_BIT)
#define REPLAY_STRUCT_VERSION_WRITE \
(REPLAY_STRUCT_VERSION_TS104000_REV1 | REPLAY_VERSION_COMPRESSION_BIT)
#define REPLAY_ALLOC_INITIAL 256
@ -95,17 +99,27 @@ typedef struct ReplayStage {
/* END REPLAY_STRUCT_VERSION_TS103000_REV2 and above */
uint64_t rng_seed; // NOTE: before REPLAY_STRUCT_VERSION_TS103000_REV2: uint32_t, also specifies start_time
uint8_t diff;
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
uint16_t skip_frames;
/* END REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
// initial player settings
uint64_t plr_points; // NOTE: before REPLAY_STRUCT_VERSION_TS103000_REV0: uint32_t
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
uint8_t plr_total_lives_used;
uint8_t plr_total_bombs_used;
/* END REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
/* BEGIN REPLAY_STRUCT_VERSION_TS102000_REV1 and above */
uint8_t plr_continues_used;
uint8_t plr_total_continues_used;
/* END REPLAY_STRUCT_VERSION_TS102000_REV1 and above */
uint8_t plr_char;
uint8_t plr_shot;
uint16_t plr_pos_x;
uint16_t plr_pos_y;
uint8_t plr_focus;
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV0 and below */
// NOTE: Not used anymore
// uint8_t plr_focus;
/* END REPLAY_STRUCT_VERSION_TS104000_REV0 and below */
uint16_t plr_power;
uint8_t plr_lives;
uint16_t plr_life_fragments; // NOTE: before REPLAY_STRUCT_VERSION_TS103000_REV1: uint8_t
@ -124,13 +138,21 @@ typedef struct ReplayStage {
uint64_t plr_points_final;
/* END REPLAY_STRUCT_VERSION_TS102000_REV3 and above */
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV0 and above */
uint8_t plr_stats_total_lives_used;
uint8_t plr_stats_stage_lives_used;
uint8_t plr_stats_total_bombs_used;
uint8_t plr_stats_stage_bombs_used;
uint8_t plr_stats_stage_continues_used;
/* END REPLAY_STRUCT_VERSION_TS104000_REV0 and above */
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV0 only */
// NOTE: These never worked correctly and were always 0
// uint8_t plr_stats_total_lives_used;
// uint8_t plr_stats_stage_lives_used;
// uint8_t plr_stats_total_bombs_used;
// uint8_t plr_stats_stage_bombs_used;
// uint8_t plr_stats_stage_continues_used;
/* END REPLAY_STRUCT_VERSION_TS104000_REV0 only */
/* BEGIN REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
// Resources used in this stage by the end of it
uint8_t plr_stage_lives_used_final;
uint8_t plr_stage_bombs_used_final;
uint8_t plr_stage_continues_used_final;
/* END REPLAY_STRUCT_VERSION_TS104000_REV1 and above */
// player input
// NOTE: only used to read the number of events from file.

View file

@ -12,7 +12,6 @@
#include "rw_common.h"
#include "rwops/rwops_autobuf.h"
#include "rwops/rwops_zlib.h"
attr_nonnull_all
static void replay_write_string(SDL_RWops *file, char *str, uint16_t version) {
@ -44,13 +43,29 @@ static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t versi
SDL_WriteLE64(file, stg->start_time);
SDL_WriteLE64(file, stg->rng_seed);
SDL_WriteU8(file, stg->diff);
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
SDL_WriteLE16(file, stg->skip_frames);
}
SDL_WriteLE64(file, stg->plr_points);
SDL_WriteU8(file, stg->plr_continues_used);
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
SDL_WriteU8(file, stg->plr_total_lives_used);
SDL_WriteU8(file, stg->plr_total_bombs_used);
}
SDL_WriteU8(file, stg->plr_total_continues_used);
SDL_WriteU8(file, stg->plr_char);
SDL_WriteU8(file, stg->plr_shot);
SDL_WriteLE16(file, stg->plr_pos_x);
SDL_WriteLE16(file, stg->plr_pos_y);
SDL_WriteU8(file, stg->plr_focus); // FIXME remove and bump version
if(version <= REPLAY_STRUCT_VERSION_TS104000_REV0) {
// NOTE: old plr_focus field
SDL_WriteU8(file, 0);
}
SDL_WriteLE16(file, stg->plr_power);
SDL_WriteU8(file, stg->plr_lives);
SDL_WriteLE16(file, stg->plr_life_fragments);
@ -64,12 +79,16 @@ static bool replay_write_stage(ReplayStage *stg, SDL_RWops *file, uint16_t versi
SDL_WriteLE64(file, stg->plr_points_final);
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV0) {
SDL_WriteU8(file, stg->plr_stats_total_lives_used);
SDL_WriteU8(file, stg->plr_stats_stage_lives_used);
SDL_WriteU8(file, stg->plr_stats_total_bombs_used);
SDL_WriteU8(file, stg->plr_stats_stage_bombs_used);
SDL_WriteU8(file, stg->plr_stats_stage_continues_used);
if(version == REPLAY_STRUCT_VERSION_TS104000_REV0) {
// NOTE: These fields were always bugged; older taisei only wrote zeroes here.
uint8_t buf[5] = { 0 };
SDL_RWwrite(file, buf, sizeof(buf), 1);
}
if(version >= REPLAY_STRUCT_VERSION_TS104000_REV1) {
SDL_WriteU8(file, stg->plr_stage_lives_used_final);
SDL_WriteU8(file, stg->plr_stage_bombs_used_final);
SDL_WriteU8(file, stg->plr_stage_continues_used_final);
}
if(stg->events.num_elements > UINT16_MAX) {
@ -122,9 +141,7 @@ bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
if(compression) {
abuf = SDL_RWAutoBuffer(&buf, 64);
vfile = SDL_RWWrapZlibWriter(
abuf, RW_DEFLATE_LEVEL_DEFAULT, REPLAY_COMPRESSION_CHUNK_SIZE, false
);
vfile = replay_wrap_stream_compress(version, abuf, false);
}
replay_write_string(vfile, rpy->playername, base_version);
@ -149,7 +166,7 @@ bool replay_write(Replay *rpy, SDL_RWops *file, uint16_t version) {
SDL_WriteLE32(file, SDL_RWtell(file) + SDL_RWtell(abuf) + 4);
SDL_RWwrite(file, buf, SDL_RWtell(abuf), 1);
SDL_RWclose(abuf);
vfile = SDL_RWWrapZlibWriter(file, RW_DEFLATE_LEVEL_DEFAULT, REPLAY_COMPRESSION_CHUNK_SIZE, false);
vfile = replay_wrap_stream_compress(version, file, false);
}
bool events_ok = replay_write_events(rpy, vfile);

View file

@ -341,7 +341,9 @@ static Replay *create_quicksave_replay(ReplayStage *rstg_src) {
dynarray_ensure_capacity(&rstg->events, rstg_src->events.num_elements + 1);
dynarray_set_elements(&rstg->events, rstg_src->events.num_elements, rstg_src->events.data);
replay_stage_event(rstg, global.frames, EV_RESUME, 0);
replay_stage_update_final_stats(rstg, &global.plr.stats);
auto rpy = ALLOC(Replay);
rpy->stages.num_elements = rpy->stages.capacity = 1;
@ -1193,6 +1195,8 @@ void stage_end_loop(void *ctx) {
if(global.gameover == GAMEOVER_WIN) {
global.replay.output.stage->flags |= REPLAY_SFLAG_CLEAR;
}
replay_stage_update_final_stats(global.replay.output.stage, &global.plr.stats);
}
}