diff --git a/src/replay/read.c b/src/replay/read.c index 869ff1d8..93499561 100644 --- a/src/replay/read.c +++ b/src/replay/read.c @@ -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; } diff --git a/src/replay/rw_common.c b/src/replay/rw_common.c index e09f4956..417c555f 100644 --- a/src/replay/rw_common.c +++ b/src/replay/rw_common.c @@ -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); + } +} diff --git a/src/replay/rw_common.h b/src/replay/rw_common.h index 2d825079..d1e4b799 100644 --- a/src/replay/rw_common.h +++ b/src/replay/rw_common.h @@ -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]; diff --git a/src/replay/stage.c b/src/replay/stage.c index f79226d6..d9e842b3 100644 --- a/src/replay/stage.c +++ b/src/replay/stage.c @@ -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) { diff --git a/src/replay/stage.h b/src/replay/stage.h index ab6ce975..1c955abf 100644 --- a/src/replay/stage.h +++ b/src/replay/stage.h @@ -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; diff --git a/src/replay/struct.h b/src/replay/struct.h index 60116b3e..708d7821 100644 --- a/src/replay/struct.h +++ b/src/replay/struct.h @@ -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. diff --git a/src/replay/write.c b/src/replay/write.c index 4098319e..3f275911 100644 --- a/src/replay/write.c +++ b/src/replay/write.c @@ -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); diff --git a/src/stage.c b/src/stage.c index 392dec01..e73ef8bd 100644 --- a/src/stage.c +++ b/src/stage.c @@ -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); } }