diff --git a/include/decl.h b/include/decl.h index d07cc3ef4..fe36e198d 100644 --- a/include/decl.h +++ b/include/decl.h @@ -404,6 +404,17 @@ struct early_opt { boolean valallowed; }; +struct ptr_array { + size_t length; + size_t max_length; + void ** elements; +}; +typedef struct ptr_array ptr_array_t; + +/* logging verbosity levels */ +#define LOG_MINIMAL 0 +#define LOG_VERBOSE 1 + /* special key functions */ enum nh_keyfunc { NHKF_ESC = 0, diff --git a/include/extern.h b/include/extern.h index 8ee9b9917..ee79b818b 100644 --- a/include/extern.h +++ b/include/extern.h @@ -28,6 +28,13 @@ E void FDECL(welcome, (BOOLEAN_P)); E time_t NDECL(get_realtime); E int FDECL(argcheck, (int, char **, enum earlyarg)); E void NDECL(early_init); +E void NDECL(fuzzer_start); +E void NDECL(fuzzer_stop); +E void NDECL(fuzzer_toggle); +E void VDECL(fuzzer_log, (int, const char *, ...)) PRINTF_F(2, 3); +E void NDECL(fuzzer_check); +E void NDECL(fuzzer_auto_start); +E boolean FDECL(fuzzer_msg_history, (const char *)); /* ### apply.c ### */ @@ -951,6 +958,7 @@ E boolean FDECL(fuzzymatch, (const char *, const char *, const char *, BOOLEAN_P)); E void FDECL(init_random, (int FDECL((*fn), (int)))); E void FDECL(reseed_random, (int FDECL((*fn), (int)))); +E void FDECL(set_random, (unsigned long, int FDECL((*fn), (int)))); E time_t NDECL(getnow); E int NDECL(getyear); #if 0 @@ -969,6 +977,8 @@ E void FDECL(strbuf_append, (strbuf_t *, const char *)); E void FDECL(strbuf_reserve, (strbuf_t *, int)); E void FDECL(strbuf_empty, (strbuf_t *)); E void FDECL(strbuf_nl_to_crlf, (strbuf_t *)); +E struct ptr_array * FDECL(ptr_array_new, (size_t length)); +E void FDECL(ptr_array_free, (struct ptr_array *)); /* ### invent.c ### */ @@ -2158,6 +2168,7 @@ E int FDECL(rnd, (int)); E int FDECL(d, (int, int)); E int FDECL(rne, (int)); E int FDECL(rnz, (int)); +E unsigned long NDECL(rul); /* ### role.c ### */ diff --git a/include/flag.h b/include/flag.h index e70672e52..2f3f7260a 100644 --- a/include/flag.h +++ b/include/flag.h @@ -447,6 +447,10 @@ struct instance_flags { chosen_windowport[], but do not switch to it in the midst of options processing */ boolean obsolete; /* obsolete options can point at this, it isn't used */ + boolean fuzzer_auto_start; /* start fuzzer automatically */ + int fuzzer_stop_and_save; /* move when fuzzer stops and saves game */ + boolean fuzzer_saving; /* fuzzer is saving game */ + int verbose_logging_start; /* move when verbose fuzzer logging starts */ }; /* diff --git a/src/allmain.c b/src/allmain.c index ce9f0e6c5..298d2805c 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -4,7 +4,7 @@ /* NetHack may be freely redistributed. See license for details. */ /* various code that was replicated in *main.c */ - +#define NEED_VARARGS #include "hack.h" #include @@ -441,6 +441,8 @@ boolean resuming; #ifdef MAIL ckmailstatus(); #endif + fuzzer_check(); + rhack((char *) 0); } if (u.utotype) /* change dungeon level */ @@ -588,6 +590,8 @@ newgame() { int i; + fuzzer_auto_start(); + #ifdef MFLOPPY gameDiskPrompt(); #endif @@ -927,4 +931,131 @@ const char *opts; #endif return; } + +static FILE * g_fuzzer_log_file = NULL; +static int g_fuzzer_log_level = LOG_MINIMAL; + +/* fuzzer_start() starts the fuzzer opening the fuzzer log file */ +void +fuzzer_start() +{ + if (!iflags.debug_fuzzer) { + const char * fq_replay; + + iflags.debug_fuzzer = TRUE; + iflags.fuzzer_auto_start = FALSE; + + nhassert(g_fuzzer_log_file == NULL); + fq_replay = fqname("fuzzer.log", SAVEPREFIX, 0); + + g_fuzzer_log_file = fopen(fq_replay, "w"); + } +} + +/* fuzzer_stop() stops the fuzzer and close the fuzzer log file */ +void +fuzzer_stop() +{ + if (iflags.debug_fuzzer) { + if(g_fuzzer_log_file != NULL) { + fclose(g_fuzzer_log_file); + g_fuzzer_log_file = NULL; + } + } +} + +/* fuzzer_toggle() toggles fuzzer state */ +void +fuzzer_toggle() +{ + if (iflags.debug_fuzzer) + fuzzer_stop(); + else + fuzzer_start(); +} + +/* fuzzer_log() is used to place messages in the file 'fuzzer.log'. This + * log is the primary tool for monitoring fuzzer activity and tracking down + * issues that the fuzzer is able to reproduce. + */ +void +fuzzer_log +VA_DECL2(int, level, const char *, str) +{ + VA_START(str); + VA_INIT(str, char *); + + if (!g_fuzzer_log_file) + return; + + if (iflags.verbose_logging_start != 0 && moves >= iflags.verbose_logging_start) + g_fuzzer_log_level = LOG_VERBOSE; + + if (level <= g_fuzzer_log_level) + Vfprintf(g_fuzzer_log_file, str, VA_ARGS); + + VA_END(); +} + +/* fuzzer_check() is called prior to rhack(0) to allow the fuzzer to + * check if it should stop and to allow it to reseed the game. + */ +void +fuzzer_check() +{ + if (iflags.debug_fuzzer) + { + if (moves >= iflags.fuzzer_stop_and_save) { + iflags.fuzzer_saving = TRUE; + dosave0(); + exit_nhwindows("Goodbye from the fuzzer..."); + fuzzer_stop(); + nh_terminate(EXIT_SUCCESS); + } + + unsigned long seed = rul(); + set_random(seed, rn2); + fuzzer_log(LOG_MINIMAL, "SEED:%ld:%lu\n", moves, seed); + + } +} + +/* fuzzer_auto_start is called when creating a new game to allow + * the fuzzer to start itself. + */ +void +fuzzer_auto_start() +{ + if (iflags.fuzzer_auto_start) { + nhassert(!iflags.debug_fuzzer); + fuzzer_start(); + unsigned long seed = rul(); + set_random(seed, rn2); + fuzzer_log(LOG_MINIMAL, "START:%ld:%lu\n", moves, seed); + } +} + +/* fuzzer_msg_history is called during save file recovery to allow + * the fuzzer to snoop the messages being recovered. The fuzzer + * saves a seed as a message in save files and this is the mechanism + * used to recover that seed if the fuzzer is being auto started. + */ +boolean +fuzzer_msg_history(msg) + const char * msg; +{ + long saved_moves; + unsigned long saved_seed; + if (sscanf(msg, "SEED:%ld:%lu", &saved_moves, &saved_seed) == 2) { + nhassert(saved_moves == moves); + if (iflags.fuzzer_auto_start) { + fuzzer_start(); + set_random(saved_seed, rn2); + fuzzer_log(LOG_MINIMAL, "START:%ld:%lu\n", moves, saved_seed); + } + return TRUE; + } + + return FALSE; +} /*allmain.c*/ diff --git a/src/dungeon.c b/src/dungeon.c index 2701731ef..a2a7d4321 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -2407,7 +2407,8 @@ recalc_mapseen() struct cemetery *bp, **bonesaddr; struct trap *t; unsigned i, ridx; - int x, y, ltyp, count, atmp; + int x, y, ltyp, count; + unsigned int atmp; /* Should not happen in general, but possible if in the process * of being booted from the quest. The mapseen object gets diff --git a/src/hacklib.c b/src/hacklib.c index e15532fca..d8d05c638 100644 --- a/src/hacklib.c +++ b/src/hacklib.c @@ -854,7 +854,7 @@ STATIC_DCL struct tm *NDECL(getlt); /* Sets the seed for the random number generator */ #ifdef USE_ISAAC64 -static void +void set_random(seed, fn) unsigned long seed; int FDECL((*fn), (int)); @@ -865,7 +865,7 @@ int FDECL((*fn), (int)); #else /* USE_ISAAC64 */ /*ARGSUSED*/ -static void +void set_random(seed, fn) unsigned long seed; int FDECL((*fn), (int)) UNUSED; @@ -917,7 +917,7 @@ int FDECL((*fn), (int)); { /* only reseed if we are certain that the seed generation is unguessable * by the players. */ - if (has_strong_rngseed) + if (has_strong_rngseed && !iflags.debug_fuzzer) init_random(fn); } @@ -1108,6 +1108,9 @@ phase_of_the_moon() /* 0-7, with 0: new, 4: full */ register struct tm *lt = getlt(); register int epact, diy, goldn; + if(iflags.debug_fuzzer) + return rn2(8); + diy = lt->tm_yday; goldn = (lt->tm_year % 19) + 1; epact = (11 * goldn + 18) % 30; @@ -1122,6 +1125,9 @@ friday_13th() { register struct tm *lt = getlt(); + if(iflags.debug_fuzzer) + return rn2(30); + /* tm_wday (day of week; 0==Sunday) == 5 => Friday */ return (boolean) (lt->tm_wday == 5 && lt->tm_mday == 13); } @@ -1223,4 +1229,38 @@ strbuf_t *strbuf; } } +ptr_array_t * +ptr_array_new(max_length) + size_t max_length; +{ + size_t esize = max_length * sizeof(void *); + ptr_array_t * a = (ptr_array_t *) malloc(sizeof(ptr_array_t) + esize); + a->elements = (void **)(a + 1); + a->length = 0; + a->max_length = max_length; + memset(a->elements, 0, esize); + return a; +} + +void +ptr_array_free(a) + ptr_array_t * a; +{ + size_t i; + + nhassert(a->length <= a->max_length); + + for(i = 0; i < a->length; i++) + if(a->elements[i]) + free(a->elements[i]); + + for (i = a->length; i < a->max_length; i++) { + nhassert(a->elements[i] == NULL); + if(a->elements[i]) + free(a->elements[i]); + } + + free(a); +} + /*hacklib.c*/ diff --git a/src/restore.c b/src/restore.c index e7ebf32b4..d53303dfb 100644 --- a/src/restore.c +++ b/src/restore.c @@ -1226,6 +1226,8 @@ register int fd; panic("restore_msghistory: msg too big (%d)", msgsize); mread(fd, (genericptr_t) msg, msgsize); msg[msgsize] = '\0'; + if(fuzzer_msg_history(msg)) + continue; putmsghistory(msg, TRUE); ++msgcount; } diff --git a/src/rnd.c b/src/rnd.c index 0ca649fbb..5473be949 100644 --- a/src/rnd.c +++ b/src/rnd.c @@ -56,10 +56,21 @@ int FDECL((*fn), (int)); (int) sizeof seed); } +unsigned long +rul() +{ + unsigned long value; + + value = (unsigned long) isaac64_next_uint64(&rnglist[CORE].rng_state); + fuzzer_log(LOG_VERBOSE, "RANDOM:%llu\n", value); + + return value; +} + static int RND(int x) { - return (isaac64_next_uint64(&rnglist[CORE].rng_state) % x); + return (rul() % x); } /* 0 <= rn2(x) < x, but on a different sequence from the "main" rn2; @@ -69,6 +80,8 @@ int rn2_on_display_rng(x) register int x; { + if (iflags.debug_fuzzer) + return rn2(x); return (isaac64_next_uint64(&rnglist[DISP].rng_state) % x); } @@ -94,6 +107,20 @@ register int x; seed *= 2739110765; return (int)((seed >> 16) % (unsigned)x); } + +unsigned long +rul() +{ +#if defined(LINT) && defined(UNIX) + return (unsigned long) rand(); +#else /* LINT */ +#if defined(UNIX) || defined(RANDOM) + return (unsigned long) Rand(); +#else + return (unsigned long) (Rand() >> 3); +#endif /* defined(UNIX) || defined(RANDOM) */ +#endif /* LINT */ +} #endif /* USE_ISAAC64 */ /* 0 <= rn2(x) < x */ diff --git a/src/save.c b/src/save.c index ecc6afcae..0b3d7e803 100644 --- a/src/save.c +++ b/src/save.c @@ -136,7 +136,7 @@ dosave0() return 0; #endif - HUP if (iflags.window_inited) { + HUP if (!iflags.debug_fuzzer && iflags.window_inited) { nh_uncompress(fq_save); fd = open_savefile(); if (fd > 0) { @@ -1236,6 +1236,18 @@ int fd, mode; bwrite(fd, (genericptr_t) msg, msglen); ++msgcount; } + /* If the fuzzer is stopping and saving, save a seed as a message. + In 3.7, we will modify the save file format and save the seed + directly in the saved game state. */ + if (iflags.fuzzer_saving) { + char message[BUFSIZ]; + unsigned long seed = rul(); + sprintf(message, "SEED:%ld:%lu", moves, seed); + fuzzer_log(LOG_MINIMAL, "STOP:%ld:%lu\n", moves, seed); + msglen = strlen(message); + bwrite(fd, (genericptr_t) &msglen, sizeof msglen); + bwrite(fd, (genericptr_t) message, msglen); + } bwrite(fd, (genericptr_t) &minusone, sizeof (int)); } debugpline1("Stored %d messages into savefile.", msgcount); diff --git a/src/sp_lev.c b/src/sp_lev.c index 19e2cd24e..898aa0030 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -3742,7 +3742,7 @@ struct opvar *mc; { int x, y; schar mapc; - xchar lit; + uchar lit; struct opvar *ret = selection_opvar((char *) 0); if (!ov || !mc || !ret) diff --git a/sys/winnt/nttty.c b/sys/winnt/nttty.c index 19cb28de7..f61201906 100644 --- a/sys/winnt/nttty.c +++ b/sys/winnt/nttty.c @@ -475,9 +475,16 @@ int *x, *y, *mod; coord cc; DWORD count; really_move_cursor(); - if (iflags.debug_fuzzer) - return randomkey(); - ch = (g.program_state.done_hup) + if (iflags.debug_fuzzer) { + int poskey = randomkey(); + + if (poskey == 0) { + *x = rn2(console.width); + *y = rn2(console.height); + } + return poskey; + } + ch = (program_state.done_hup) ? '\033' : keyboard_handler.pCheckInput( console.hConIn, &ir, &count, iflags.num_pad, 1, mod, &cc); diff --git a/sys/winnt/windmain.c b/sys/winnt/windmain.c index 4aad72a87..e163c147d 100644 --- a/sys/winnt/windmain.c +++ b/sys/winnt/windmain.c @@ -335,7 +335,7 @@ attempt_restore: resuming = TRUE; /* not starting new game */ if (discover) You("are in non-scoring discovery mode."); - if (discover || wizard) { + if ((discover || wizard) && !iflags.fuzzer_auto_start) { if (yn("Do you want to keep the save file?") == 'n') (void) delete_savefile(); else { @@ -460,6 +460,23 @@ char *argv[]; case 'X': discover = TRUE, wizard = FALSE; break; + case 'F': + { + iflags.fuzzer_auto_start = 1; + + if (argc > 1 && argv[1][0] != '-') { + argc--; + argv++; + iflags.fuzzer_stop_and_save = atoi(*argv); + + if (argc > 1 && argv[1][0] != '-') { + argc--; + argv++; + iflags.verbose_logging_start = atoi(*argv); + } + } + } + break; #ifdef NEWS case 'n': iflags.news = FALSE; diff --git a/sys/winnt/winnt.c b/sys/winnt/winnt.c index 8d513c59e..39978cab4 100644 --- a/sys/winnt/winnt.c +++ b/sys/winnt/winnt.c @@ -718,9 +718,19 @@ nt_assert_failed(expression, filepath, line) { const char * filename; - /* get file name from path */ filename = strrchr(filepath, '\\'); filename = (filename == NULL ? filepath : filename + 1); + + if (IsDebuggerPresent()) { + char message[BUFSIZ]; + snprintf(message, sizeof(message), + "nhassert(%s) failed in file '%s' at line %d", + expression, filename, line); + OutputDebugStringA(message); + DebugBreak(); + } + + /* get file name from path */ impossible("nhassert(%s) failed in file '%s' at line %d", expression, filename, line); } diff --git a/win/tty/topl.c b/win/tty/topl.c index 5cb888999..fa25b0ec6 100644 --- a/win/tty/topl.c +++ b/win/tty/topl.c @@ -259,7 +259,7 @@ register const char *bp; && cw->cury == 0 && n0 + (int) strlen(g.toplines) + 3 < CO - 8 /* room for --More-- */ && (notdied = strncmp(bp, "You die", 7)) != 0) { - nhassert((long) strlen(g.toplines) == cw->curx); + /* nhassert((long) strlen(g.toplines) == cw->curx); */ Strcat(g.toplines, " "); Strcat(g.toplines, bp); cw->curx += 2; @@ -617,6 +617,66 @@ boolean purged; /* True: took history's pointers, False: just cloned them */ } } +STATIC_OVL ptr_array_t * +get_message_history() +{ + char *mesg; + int i; + struct WinDesc *cw; + size_t max_length; + ptr_array_t * a; + + nhassert(WIN_MESSAGE != WIN_ERR); + nhassert(wins[WIN_MESSAGE] != NULL); + + /* paranoia (too early or too late panic save attempt?) */ + if (WIN_MESSAGE == WIN_ERR || !wins[WIN_MESSAGE]) + return NULL; + + cw = wins[WIN_MESSAGE]; + + max_length = cw->rows; + + if (*toplines) max_length++; + + a = ptr_array_new(max_length); + + nhassert(cw->maxrow <= cw->rows); + for (i = 0; i < cw->rows; ++i) { + mesg = cw->data[(i + cw->maxrow) % cw->rows]; + if (mesg && *mesg) + a->elements[a->length++] = strdup(mesg); + } + if (*toplines) + a->elements[a->length++] = strdup(toplines); + + return a; +} + +STATIC_OVL void +purge_message_history() +{ + int i; + struct WinDesc *cw; + + nhassert(WIN_MESSAGE != WIN_ERR); + nhassert(wins[WIN_MESSAGE] != NULL); + + cw = wins[WIN_MESSAGE]; + + *toplines = '\0'; + + for (i = 0; i < cw->rows; ++i) { + if (cw->data[i]) { + free(cw->data[i]); + cw->data[i] = (char *) 0; + cw->datlen[i] = 0; + } + } + + cw->maxcol = cw->maxrow = 0; +} + /* * This is called by the core save routines. * Each time we are called, we return one string from the @@ -631,21 +691,25 @@ char * tty_getmsghistory(init) boolean init; { - static int nxtidx; - char *nextmesg; - char *result = 0; + static size_t nxtidx; + static ptr_array_t * saved_messages = NULL; + char *result = NULL; if (init) { - msghistory_snapshot(FALSE); + nhassert(saved_messages == NULL); + saved_messages = get_message_history(); nxtidx = 0; + wins[WIN_MESSAGE]->flags |= WIN_LOCKHISTORY; } - if (snapshot_mesgs) { - nextmesg = snapshot_mesgs[nxtidx++]; - if (nextmesg) { - result = (char *) nextmesg; - } else { - free_msghistory_snapshot(FALSE); + if (saved_messages) { + if (nxtidx < saved_messages->length) + result = saved_messages->elements[nxtidx++]; + + if (result == NULL) { + ptr_array_free(saved_messages); + saved_messages = NULL; + wins[WIN_MESSAGE]->flags &= ~WIN_LOCKHISTORY; } } return result; @@ -674,7 +738,10 @@ const char *msg; boolean restoring_msghist; { static boolean initd = FALSE; - int idx; + static ptr_array_t * saved_messages = NULL; + size_t i; + + nhassert(!(wins[WIN_MESSAGE]->flags & WIN_LOCKHISTORY)); if (restoring_msghist && !initd) { /* we're restoring history from the previous session, but new @@ -682,7 +749,9 @@ boolean restoring_msghist; for instance); collect current history (ie, those new messages), and also clear it out so that nothing will be present when the restored ones are being put into place */ - msghistory_snapshot(TRUE); + nhassert(saved_messages == NULL); + saved_messages = get_message_history(); + purge_message_history(); initd = TRUE; #ifdef DUMPLOG /* this suffices; there's no need to scrub saved_pline[] pointers */ @@ -704,22 +773,39 @@ boolean restoring_msghist; #ifdef DUMPLOG dumplogmsg(g.toplines); #endif - } else if (snapshot_mesgs) { - nhassert(ttyDisplay == NULL || - ttyDisplay->toplin != TOPLINE_NEED_MORE); - - /* done putting arbitrary messages in; put the snapshot ones back */ - for (idx = 0; snapshot_mesgs[idx]; ++idx) { - remember_topl(); - Strcpy(g.toplines, snapshot_mesgs[idx]); -#ifdef DUMPLOG - dumplogmsg(g.toplines); -#endif - } - /* now release the snapshot */ - free_msghistory_snapshot(TRUE); - initd = FALSE; /* reset */ + return; } + + /* tty_putmsghistory() is called with msg==NULL to indidate that + * we are finished restoring the message history. If we saved + * some message history when we started, its time now to appened + * those saved messages. + * + * We don't otherwise expect to get called with msg==NULL. + * + */ + nhassert(restoring_msghist && initd); + + if (initd && restoring_msghist) { + if (saved_messages) { + nhassert(ttyDisplay == NULL || + ttyDisplay->toplin != TOPLINE_NEED_MORE); + + for (i = 0; i < saved_messages->length; i++) { + const char * mesg = saved_messages->elements[i]; + remember_topl(); + nhassert(mesg); + Strcpy(g.toplines, mesg); +#ifdef DUMPLOG + dumplogmsg(g.toplines); +#endif + } + ptr_array_free(saved_messages); + saved_messages = NULL; + } + initd = FALSE; + } + } #endif /* TTY_GRAPHICS */ diff --git a/win/win32/scripts/fuzzer/clean.bat b/win/win32/scripts/fuzzer/clean.bat new file mode 100644 index 000000000..a02c48739 --- /dev/null +++ b/win/win32/scripts/fuzzer/clean.bat @@ -0,0 +1,8 @@ +set BIN_DIR=..\..\..\..\bin\Debug\Win32 + +set FUZZER_LOG=%BIN_DIR%\fuzzer.log +set FUZZER_DIR=%BIN_DIR%\fuzzer + +if exist %BIN_DIR%\%USERNAME%* del %BIN_DIR%\%USERNAME%* +if exist %FUZZER_LOG% del %FUZZER_LOG% + diff --git a/win/win32/scripts/fuzzer/longtest.bat b/win/win32/scripts/fuzzer/longtest.bat new file mode 100644 index 000000000..e5e8d1529 --- /dev/null +++ b/win/win32/scripts/fuzzer/longtest.bat @@ -0,0 +1,23 @@ +echo off + +SETLOCAL ENABLEEXTENSIONS +SETLOCAL ENABLEDELAYEDEXPANSION + +set STEP_SIZE=5000 +set FINAL_MOVE=500000 +set START_MOVE=5000 + +for /L %%i in (%START_MOVE%, %STEP_SIZE%, %FINAL_MOVE%) do ( + + call runtill.bat %%i + + if ERRORLEVEL 1 ( + echo FAILED getting running to %%i. + exit /b 1 + ) + +) + +echo SUCCESS. + + diff --git a/win/win32/scripts/fuzzer/restore.bat b/win/win32/scripts/fuzzer/restore.bat new file mode 100644 index 000000000..9752fed96 --- /dev/null +++ b/win/win32/scripts/fuzzer/restore.bat @@ -0,0 +1,7 @@ +call clean.bat + +set BIN_DIR=..\..\..\..\bin\Debug\Win32 +set SAVED_GAME=%USERNAME%-wizard.NetHack-saved-game +set FUZZER_DIR=%BIN_DIR%\fuzzer + +copy %FUZZER_DIR%\%SAVED_GAME% %BIN_DIR%\%SAVED_GAME% diff --git a/win/win32/scripts/fuzzer/runtill.bat b/win/win32/scripts/fuzzer/runtill.bat new file mode 100644 index 000000000..ebb5aa4b6 --- /dev/null +++ b/win/win32/scripts/fuzzer/runtill.bat @@ -0,0 +1,73 @@ +REM +REM runtill target_move +REM +echo off + +SETLOCAL ENABLEEXTENSIONS +SETLOCAL ENABLEDELAYEDEXPANSION + +set TARGET_MOVE=%1 + +if %TARGET_MOVE% == "" ( + echo Usage:runtill target_move + goto :eof +) + +set BIN_DIR=..\..\..\..\bin\Debug\Win32 +set SAVED_GAME=%USERNAME%-wizard.NetHack-saved-game +set LOG_FILE=%BIN_DIR%\runtil.log +set FUZZER_LOG=%BIN_DIR%\fuzzer.log +set FUZZER_DIR=%BIN_DIR%\fuzzer +set BASELINE=%FUZZER_DIR%\fuzzer.log + +if not exist %FUZZER_DIR% mkdir %FUZZER_DIR% + +call clean.bat + +if not exist %FUZZER_DIR%\%SAVED_GAME% ( + %BIN_DIR%\nethack -D -F 0 + + copy %BIN_DIR%\%SAVED_GAME% %FUZZER_DIR% +) + +call restore.bat + +%BIN_DIR%\nethack -D -F %TARGET_MOVE% + +move %BIN_DIR%\*.snap %BIN_DIR%\snapshots +copy %FUZZER_LOG% %BASELINE% + +for /f "tokens=2,3 delims=: usebackq" %%i in (`findstr /c:START %BASELINE%`) do ( + set START_SEED=%%j + set START_MOVE=%%i +) + +for /f "tokens=2,3 delims=: usebackq" %%i in (`findstr /c:STOP %BASELINE%`) do ( + set STOP_SEED=%%j + set STOP_MOVE=%%i +) + +if !STOP_MOVE! LSS %TARGET_MOVE% ( + cls + echo FAILED: Failed to reach target move. !STOP_MOVE! is not GTE %TARGET_MOVE%. + exit /b 1 +) + +call restore.bat + +%BIN_DIR%\nethack -D -F %TARGET_MOVE% + +fc %FUZZER_LOG% %BASELINE% + +if ERRORLEVEL 1 ( + cls + echo FAILED: Unable to reproduce same timeline + exit /b 1 +) + +del /q %FUZZER_DIR%\%SAVED_GAME% + +copy %BIN_DIR%\%SAVED_GAME% %FUZZER_DIR% + +echo !START_MOVE! to !STOP_MOVE!. +echo SUCCESS.