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:
parent
173c8c3cc6
commit
3d4226ce04
4 changed files with 83 additions and 14 deletions
21
src/cli.c
21
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;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ struct CLIAction {
|
|||
int frameskip;
|
||||
CutsceneID cutscene;
|
||||
char *filename;
|
||||
char *out_replay;
|
||||
PlayerMode *plrmode;
|
||||
};
|
||||
|
||||
|
|
69
src/main.c
69
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue