From 90f5aa91b794ad333fc04693677d0a7a9d8ca18e Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 09:52:56 -0700 Subject: [PATCH 1/8] Fix compiler warnings. --- src/dungeon.c | 3 ++- src/sp_lev.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dungeon.c b/src/dungeon.c index 27b748104..0e483a11d 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -2409,7 +2409,8 @@ recalc_mapseen() struct monst *mtmp; struct cemetery *bp, **bonesaddr; 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/sp_lev.c b/src/sp_lev.c index dbab1dfdc..b2206ae70 100644 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -3763,7 +3763,7 @@ struct opvar *mc; { int x, y; schar mapc; - xchar lit; + uchar lit; struct opvar *ret = selection_opvar((char *) 0); if (!ov || !mc || !ret) From c44ad5645d90267e7ed1d1a6c57acc1437dca10e Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 10:53:53 -0700 Subject: [PATCH 2/8] Removing assertion that does not hold under all scenarios. When we save gamestate as part of making an insurance snapshot, we will save message history which will clear toplines but leaving window state in tack including the need for more. --- win/tty/topl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win/tty/topl.c b/win/tty/topl.c index cfd17838d..74331dece 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(toplines) + 3 < CO - 8 /* room for --More-- */ && (notdied = strncmp(bp, "You die", 7)) != 0) { - nhassert((long) strlen(toplines) == cw->curx); + /* nhassert((long) strlen(toplines) == cw->curx); */ Strcat(toplines, " "); Strcat(toplines, bp); cw->curx += 2; From e665d3b85024e3a7580e80888a70b47d5ae9384c Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 16:08:08 -0700 Subject: [PATCH 3/8] Adding ptr_array data structure. --- include/decl.h | 7 +++++++ include/extern.h | 2 ++ src/hacklib.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/include/decl.h b/include/decl.h index 5ebaa326e..834b14ab8 100644 --- a/include/decl.h +++ b/include/decl.h @@ -455,6 +455,13 @@ struct early_opt { boolean valallowed; }; +struct ptr_array { + size_t length; + size_t max_length; + void ** elements; +}; +typedef struct ptr_array ptr_array_t; + #undef E #endif /* DECL_H */ diff --git a/include/extern.h b/include/extern.h index abea20a68..d2ed2ff9e 100644 --- a/include/extern.h +++ b/include/extern.h @@ -968,6 +968,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 ### */ diff --git a/src/hacklib.c b/src/hacklib.c index b97a78a65..9fe316517 100644 --- a/src/hacklib.c +++ b/src/hacklib.c @@ -1223,4 +1223,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*/ From 1d0b8b4680512a85a045006b3adf7fe65100f1bd Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 15:57:06 -0700 Subject: [PATCH 4/8] Re-worked tty_putmsgistory and tty_getmsghistory. When we were saving message history as part of a game save for insurance, we were calling remember_topl() and thus inappropriately changing topline state. This would cause us to mis-manage the topline in subsequent calls to update the topline. The code has been re-worked to fix the issue, reduce complexity and make the code clearer. --- win/tty/topl.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/win/tty/topl.c b/win/tty/topl.c index 74331dece..b19bc0700 100644 --- a/win/tty/topl.c +++ b/win/tty/topl.c @@ -617,6 +617,7 @@ boolean purged; /* True: took history's pointers, False: just cloned them */ } } +#if 0 /* * This is called by the core save routines. * Each time we are called, we return one string from the @@ -724,6 +725,201 @@ boolean restoring_msghist; initd = FALSE; /* reset */ } } +#else +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 + * message history starting with the oldest message first. + * When none are left, we return a final null string. + * + * History is collected at the time of the first call. + * Any new messages issued after that point will not be + * included among the output of the subsequent calls. + */ +char * +tty_getmsghistory(init) +boolean init; +{ + static size_t nxtidx; + static ptr_array_t * saved_messages = NULL; + char *result = NULL; + + if (init) { + nhassert(saved_messages == NULL); + saved_messages = get_message_history(); + nxtidx = 0; + wins[WIN_MESSAGE]->flags |= WIN_LOCKHISTORY; + } + + 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; +} + +/* + * This is called by the core savefile restore routines. + * Each time we are called, we stuff the string into our message + * history recall buffer. The core will send the oldest message + * first (actually it sends them in the order they exist in the + * save file, but that is supposed to be the oldest first). + * These messages get pushed behind any which have been issued + * since this session with the program has been started, since + * they come from a previous session and logically precede + * anything (like "Restoring save file...") that's happened now. + * + * Called with a null pointer to finish up restoration. + * + * It's also called by the quest pager code when a block message + * has a one-line summary specified. We put that line directly + * into message history for ^P recall without having displayed it. + */ +void +tty_putmsghistory(msg, restoring_msghist) +const char *msg; +boolean restoring_msghist; +{ + static boolean initd = FALSE; + static ptr_array_t * saved_messages = NULL; + size_t i; +#ifdef DUMPLOG + extern unsigned saved_pline_index; /* pline.c */ +#endif + + nhassert(!(wins[WIN_MESSAGE]->flags & WIN_LOCKHISTORY)); + + if (restoring_msghist && !initd) { + /* we're restoring history from the previous session, but new + messages have already been issued this session ("Restoring...", + 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 */ + 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 */ + saved_pline_index = 0; +#endif + } + + if (msg) { + /* Caller is asking us to remember a top line that needed more. + Should we call more? This can happen when the player has set + iflags.force_invmenu and they attempt to shoot with nothing in + the quiver. */ + if (ttyDisplay && ttyDisplay->toplin == TOPLINE_NEED_MORE) + ttyDisplay->toplin = TOPLINE_NON_EMPTY; + + /* move most recent message to history, make this become most recent */ + remember_topl(); + Strcpy(toplines, msg); +#ifdef DUMPLOG + dumplogmsg(toplines); +#endif + 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; ilength; i++) { + const char * mesg = saved_messages->elements[i]; + remember_topl(); + nhassert(mesg); + Strcpy(toplines, mesg); +#ifdef DUMPLOG + dumplogmsg(toplines); +#endif + } + ptr_array_free(saved_messages); + saved_messages = NULL; + } + initd = FALSE; + } + +} +#endif #endif /* TTY_GRAPHICS */ From 5d2872dd4f24fb76f29bf34f6f950394910d6d33 Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 16:00:14 -0700 Subject: [PATCH 5/8] Add stopping in the debugger when nhassert() is hit in the windows port. When stopping in the debugger after having called impossible, the windowing state will have been modified since the assertion was hit. This made examining state that caused the nhassert to fire no longer possible. To avoid this issue, we now detect the debugger and stop in the debugger prior to impossible. --- sys/winnt/winnt.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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); } From aa95e20ca7cbb2a90f8076f04410886c0733dc74 Mon Sep 17 00:00:00 2001 From: Bart House Date: Sat, 13 Jul 2019 16:23:37 -0700 Subject: [PATCH 6/8] Removed the older version of tty_putmsghistory and tty_getmsghistory. --- win/tty/topl.c | 110 ------------------------------------------------- 1 file changed, 110 deletions(-) diff --git a/win/tty/topl.c b/win/tty/topl.c index b19bc0700..db151265f 100644 --- a/win/tty/topl.c +++ b/win/tty/topl.c @@ -617,115 +617,6 @@ boolean purged; /* True: took history's pointers, False: just cloned them */ } } -#if 0 -/* - * This is called by the core save routines. - * Each time we are called, we return one string from the - * message history starting with the oldest message first. - * When none are left, we return a final null string. - * - * History is collected at the time of the first call. - * Any new messages issued after that point will not be - * included among the output of the subsequent calls. - */ -char * -tty_getmsghistory(init) -boolean init; -{ - static int nxtidx; - char *nextmesg; - char *result = 0; - - if (init) { - msghistory_snapshot(FALSE); - nxtidx = 0; - } - - if (snapshot_mesgs) { - nextmesg = snapshot_mesgs[nxtidx++]; - if (nextmesg) { - result = (char *) nextmesg; - } else { - free_msghistory_snapshot(FALSE); - } - } - return result; -} - -/* - * This is called by the core savefile restore routines. - * Each time we are called, we stuff the string into our message - * history recall buffer. The core will send the oldest message - * first (actually it sends them in the order they exist in the - * save file, but that is supposed to be the oldest first). - * These messages get pushed behind any which have been issued - * since this session with the program has been started, since - * they come from a previous session and logically precede - * anything (like "Restoring save file...") that's happened now. - * - * Called with a null pointer to finish up restoration. - * - * It's also called by the quest pager code when a block message - * has a one-line summary specified. We put that line directly - * into message history for ^P recall without having displayed it. - */ -void -tty_putmsghistory(msg, restoring_msghist) -const char *msg; -boolean restoring_msghist; -{ - static boolean initd = FALSE; - int idx; -#ifdef DUMPLOG - extern unsigned saved_pline_index; /* pline.c */ -#endif - - if (restoring_msghist && !initd) { - /* we're restoring history from the previous session, but new - messages have already been issued this session ("Restoring...", - 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); - initd = TRUE; -#ifdef DUMPLOG - /* this suffices; there's no need to scrub saved_pline[] pointers */ - saved_pline_index = 0; -#endif - } - - if (msg) { - /* Caller is asking us to remember a top line that needed more. - Should we call more? This can happen when the player has set - iflags.force_invmenu and they attempt to shoot with nothing in - the quiver. */ - if (ttyDisplay && ttyDisplay->toplin == TOPLINE_NEED_MORE) - ttyDisplay->toplin = TOPLINE_NON_EMPTY; - - /* move most recent message to history, make this become most recent */ - remember_topl(); - Strcpy(toplines, msg); -#ifdef DUMPLOG - dumplogmsg(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(toplines, snapshot_mesgs[idx]); -#ifdef DUMPLOG - dumplogmsg(toplines); -#endif - } - /* now release the snapshot */ - free_msghistory_snapshot(TRUE); - initd = FALSE; /* reset */ - } -} -#else STATIC_OVL ptr_array_t * get_message_history() { @@ -919,7 +810,6 @@ boolean restoring_msghist; } } -#endif #endif /* TTY_GRAPHICS */ From 435f1c46267a194e44f0b75c7348be5a900d8ede Mon Sep 17 00:00:00 2001 From: Bart House Date: Sun, 14 Jul 2019 00:20:09 -0700 Subject: [PATCH 7/8] Fuzzer improvements. phase_of_moon and friday_13th determined using rn2() instead of local time if fuzzing. Don't reseed using init_random() if fuzzing. Allow set_random to be called outside of hacklib. rn2_on_display_rng uses rn2 if fuzzing so that we have a single source of random that we can ensure is reproducible. Implement rul() that returns a random unsigned long. Fix bug in fuzzer handling of ntposkey which would cause us to use unitialized values for x and y. Added command line arguments to allow auto starting and stopping of fuzzer. Add a logging facility for the fuzzer to use to record activity. Added some scripts used to automate fuzzer testing on windows. --- include/decl.h | 4 + include/extern.h | 9 ++ include/flag.h | 4 + src/allmain.c | 131 ++++++++++++++++++++++++++ src/hacklib.c | 12 ++- src/restore.c | 2 + src/rnd.c | 29 +++++- src/save.c | 14 ++- sys/winnt/nttty.c | 10 +- sys/winnt/windmain.c | 19 +++- win/win32/scripts/fuzzer/clean.bat | 8 ++ win/win32/scripts/fuzzer/longtest.bat | 23 +++++ win/win32/scripts/fuzzer/restore.bat | 7 ++ win/win32/scripts/fuzzer/runtill.bat | 73 ++++++++++++++ 14 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 win/win32/scripts/fuzzer/clean.bat create mode 100644 win/win32/scripts/fuzzer/longtest.bat create mode 100644 win/win32/scripts/fuzzer/restore.bat create mode 100644 win/win32/scripts/fuzzer/runtill.bat diff --git a/include/decl.h b/include/decl.h index 834b14ab8..711ba614e 100644 --- a/include/decl.h +++ b/include/decl.h @@ -462,6 +462,10 @@ struct ptr_array { }; typedef struct ptr_array ptr_array_t; +/* logging verbosity levels */ +#define LOG_MINIMAL 0 +#define LOG_VERBOSE 1 + #undef E #endif /* DECL_H */ diff --git a/include/extern.h b/include/extern.h index d2ed2ff9e..3c74bfda0 100644 --- a/include/extern.h +++ b/include/extern.h @@ -27,6 +27,13 @@ E void NDECL(newgame); E void FDECL(welcome, (BOOLEAN_P)); E time_t NDECL(get_realtime); E int FDECL(argcheck, (int, char **, enum earlyarg)); +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 ### */ @@ -950,6 +957,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 @@ -2161,6 +2169,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 3ea3ada26..50f76c457 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 082447b3b..cde2ef9a2 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -436,6 +436,8 @@ boolean resuming; #ifdef MAIL ckmailstatus(); #endif + fuzzer_check(); + rhack((char *) 0); } if (u.utotype) /* change dungeon level */ @@ -583,6 +585,8 @@ newgame() { int i; + fuzzer_auto_start(); + #ifdef MFLOPPY gameDiskPrompt(); #endif @@ -922,4 +926,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/hacklib.c b/src/hacklib.c index 9fe316517..1f43325df 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); } diff --git a/src/restore.c b/src/restore.c index b6126a227..7d1f6d894 100644 --- a/src/restore.c +++ b/src/restore.c @@ -1231,6 +1231,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 e0af4b8d2..e931eae76 100644 --- a/src/save.c +++ b/src/save.c @@ -139,7 +139,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) { @@ -1238,6 +1238,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/sys/winnt/nttty.c b/sys/winnt/nttty.c index a8000bc05..4cca4dcbd 100644 --- a/sys/winnt/nttty.c +++ b/sys/winnt/nttty.c @@ -475,8 +475,14 @@ int *x, *y, *mod; coord cc; DWORD count; really_move_cursor(); - if (iflags.debug_fuzzer) - return randomkey(); + 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( diff --git a/sys/winnt/windmain.c b/sys/winnt/windmain.c index b2bb18e79..d1712ffe4 100644 --- a/sys/winnt/windmain.c +++ b/sys/winnt/windmain.c @@ -336,7 +336,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 { @@ -461,6 +461,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/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. From d33160d069c00e26de902e48c2bfda2de2eeeb3d Mon Sep 17 00:00:00 2001 From: Bart House Date: Sun, 14 Jul 2019 02:00:21 -0700 Subject: [PATCH 8/8] Fix build issue on linux. --- src/allmain.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmain.c b/src/allmain.c index cde2ef9a2..957c47a44 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