From 3d4226ce044e487aaa8d0aa4eead3cae8d4c1fc0 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev Date: Wed, 16 Jun 2021 01:39:48 +0300 Subject: [PATCH] replay/cli: --rereplay option "Re-records" a replay into a file. TAISEI_REPLAY_DESYNC_CHECK_FREQUENCY is defaulted to 1 in this mode, but may be overriden as normal. Requires -r or -R; in case of -R will not stop even if a desync is encountered. --- src/cli.c | 21 ++++++++++++---- src/cli.h | 1 + src/main.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++------- src/stage.c | 6 ++++- 4 files changed, 83 insertions(+), 14 deletions(-) diff --git a/src/cli.c b/src/cli.c index b5d57cae..a655473b 100644 --- a/src/cli.c +++ b/src/cli.c @@ -20,13 +20,14 @@ #include "cutscenes/cutscene.h" #include "cutscenes/scenes.h" -struct TsOption { struct option opt; const char *help; const char *argname;}; +struct TsOption { struct option opt; const char *help; const char *argname; }; enum { OPT_RENDERER = INT_MIN, OPT_CUTSCENE, OPT_CUTSCENE_LIST, OPT_FORCE_INTRO, + OPT_REREPLAY, }; static void print_help(struct TsOption* opts) { @@ -74,7 +75,8 @@ int cli_args(int argc, char **argv, CLIAction *a) { struct TsOption taisei_opts[] = { {{"replay", required_argument, 0, 'r'}, "Play a replay from %s", "FILE"}, - {{"verify-replay", required_argument, 0, 'R'}, "Play a replay from %s in headless mode, crash as soon as it desyncs", "FILE"}, + {{"verify-replay", required_argument, 0, 'R'}, "Play a replay from %s in headless mode, crash as soon as it desyncs unless --rereplay is used", "FILE"}, + {{"rereplay", required_argument, 0, OPT_REREPLAY}, "Re-record replay into %s; specify input with -r or -R", "OUTFILE"}, #ifdef DEBUG {{"play", no_argument, 0, 'p'}, "Play a specific stage"}, {{"sid", required_argument, 0, 'i'}, "Select stage by %s", "ID"}, @@ -97,7 +99,7 @@ int cli_args(int argc, char **argv, CLIAction *a) { memset(a, 0, sizeof(*a)); - int nopts = sizeof(taisei_opts)/sizeof(taisei_opts[0]); + int nopts = ARRAY_SIZE(taisei_opts); struct option opts[nopts]; char optc[2*nopts+1]; char *ptr = optc; @@ -152,6 +154,10 @@ int cli_args(int argc, char **argv, CLIAction *a) { a->type = CLI_VerifyReplay; a->filename = strdup(optarg); break; + case OPT_REREPLAY: + a->out_replay = strdup(optarg); + env_set("TAISEI_REPLAY_DESYNC_CHECK_FREQUENCY", 1, false); + break; case 'p': a->type = CLI_SelectStage; break; @@ -271,8 +277,13 @@ int cli_args(int argc, char **argv, CLIAction *a) { a->stageid = stageid; - if(a->type == CLI_SelectStage && !stageid) + if(a->type == CLI_SelectStage && !stageid) { log_fatal("StageSelect mode, but no stage id was given"); + } + + if(a->out_replay && a->type != CLI_PlayReplay && a->type != CLI_VerifyReplay) { + log_fatal("--rereplay requires --replay or --verify-replay"); + } return 0; } @@ -280,4 +291,6 @@ int cli_args(int argc, char **argv, CLIAction *a) { void free_cli_action(CLIAction *a) { free(a->filename); a->filename = NULL; + free(a->out_replay); + a->out_replay = NULL; } diff --git a/src/cli.h b/src/cli.h index a006aeec..a2944758 100644 --- a/src/cli.h +++ b/src/cli.h @@ -34,6 +34,7 @@ struct CLIAction { int frameskip; CutsceneID cutscene; char *filename; + char *out_replay; PlayerMode *plrmode; }; diff --git a/src/main.c b/src/main.c index daceac7e..9c72127f 100644 --- a/src/main.c +++ b/src/main.c @@ -183,7 +183,9 @@ static void log_version(void) { typedef struct MainContext { CLIAction cli; - Replay replay; + Replay *replay_in; + Replay *replay_out; + SDL_RWops *replay_out_stream; int replay_idx; uchar headless : 1; } MainContext; @@ -194,9 +196,40 @@ static void main_singlestg(MainContext *mctx) attr_unused; static void main_replay(MainContext *mctx); static noreturn void main_vfstree(CallChainResult ccr); +static void cleanup_replay(Replay **rpy) { + if(*rpy) { + replay_reset(*rpy); + free(*rpy); + *rpy = NULL; + } +} + +static Replay *alloc_replay(void) { + return calloc(1, sizeof(Replay)); +} + static noreturn void main_quit(MainContext *ctx, int status) { free_cli_action(&ctx->cli); - replay_reset(&ctx->replay); + + cleanup_replay(&ctx->replay_in); + + if(ctx->replay_out_stream) { + if(ctx->replay_out) { + if(!replay_write( + ctx->replay_out, + ctx->replay_out_stream, + REPLAY_STRUCT_VERSION_WRITE + )) { + log_fatal("replay_write() failed"); + } + } + + SDL_RWclose(ctx->replay_out_stream); + ctx->replay_out_stream = NULL; + } + + cleanup_replay(&ctx->replay_out); + free(ctx); exit(status); } @@ -238,11 +271,13 @@ int main(int argc, char **argv) { } if(ctx->cli.type == CLI_PlayReplay || ctx->cli.type == CLI_VerifyReplay) { - if(!replay_load_syspath(&ctx->replay, ctx->cli.filename, REPLAY_READ_ALL)) { + ctx->replay_in = alloc_replay(); + + if(!replay_load_syspath(ctx->replay_in, ctx->cli.filename, REPLAY_READ_ALL)) { main_quit(ctx, 1); } - ctx->replay_idx = ctx->cli.stageid ? replay_find_stage_idx(&ctx->replay, ctx->cli.stageid) : 0; + ctx->replay_idx = ctx->cli.stageid ? replay_find_stage_idx(ctx->replay_in, ctx->cli.stageid) : 0; if(ctx->replay_idx < 0) { main_quit(ctx, 1); @@ -251,6 +286,16 @@ int main(int argc, char **argv) { if(ctx->cli.type == CLI_VerifyReplay) { ctx->headless = true; } + + if(ctx->cli.out_replay != NULL) { + ctx->replay_out_stream = SDL_RWFromFile(ctx->cli.out_replay, "wb"); + + if(!ctx->replay_out_stream) { + log_sdl_error(LOG_FATAL, "SDL_RWFromFile"); + } + + ctx->replay_out = alloc_replay(); + } } else if(ctx->cli.type == CLI_DumpVFSTree) { vfs_setup(CALLCHAIN(main_vfstree, ctx)); return 0; // NO main_quit here! vfs_setup may be asynchronous. @@ -365,8 +410,9 @@ static void main_singlestg_begin_game(CallChainResult ccr) { SingleStageContext *ctx = ccr.ctx; MainContext *mctx = ctx->mctx; - replay_reset(&mctx->replay); - replay_state_init_record(&global.replay.output, &mctx->replay); + mctx->replay_out = alloc_replay(); + replay_reset(mctx->replay_out); + replay_state_init_record(&global.replay.output, mctx->replay_out); replay_state_deinit(&global.replay.input); global.gameover = 0; player_init(&global.plr); @@ -384,10 +430,10 @@ static void main_singlestg_end_game(CallChainResult ccr) { MainContext *mctx = ctx->mctx; if(global.gameover == GAMEOVER_RESTART) { - replay_reset(&mctx->replay); + replay_reset(mctx->replay_out); main_singlestg_begin_game(ccr); } else { - ask_save_replay(&mctx->replay, CALLCHAIN(main_singlestg_cleanup, ccr.ctx)); + ask_save_replay(mctx->replay_out, CALLCHAIN(main_singlestg_cleanup, ccr.ctx)); } } @@ -427,7 +473,12 @@ static void main_singlestg(MainContext *mctx) { } static void main_replay(MainContext *mctx) { - replay_play(&mctx->replay, mctx->replay_idx, CALLCHAIN(main_cleanup, mctx)); + if(mctx->replay_out) { + replay_state_init_record(&global.replay.output, mctx->replay_out); + stralloc(&mctx->replay_out->playername, mctx->replay_in->playername); + } + + replay_play(mctx->replay_in, mctx->replay_idx, CALLCHAIN(main_cleanup, mctx)); eventloop_run(); } diff --git a/src/stage.c b/src/stage.c index e50f069b..8c3208e3 100644 --- a/src/stage.c +++ b/src/stage.c @@ -785,7 +785,11 @@ static LogicFrameAction stage_logic_frame(void *arg) { replay_stage_event(global.replay.output.stage, global.frames, EV_CHECK_DESYNC, desync_check); } - if(rpsync == REPLAY_SYNC_FAIL && global.is_replay_verification) { + if( + rpsync == REPLAY_SYNC_FAIL && + global.is_replay_verification && + !global.replay.output.stage + ) { exit(1); }