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.
This commit is contained in:
Andrei Alexeyev 2021-06-16 01:39:48 +03:00
parent 173c8c3cc6
commit 3d4226ce04
No known key found for this signature in database
GPG key ID: 72D26128040B9690
4 changed files with 83 additions and 14 deletions

View file

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

View file

@ -34,6 +34,7 @@ struct CLIAction {
int frameskip;
CutsceneID cutscene;
char *filename;
char *out_replay;
PlayerMode *plrmode;
};

View file

@ -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();
}

View file

@ -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);
}