From 51486d0c02492933080de8a689b3de9e5c693799 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 7 Jun 2026 01:31:19 -0400 Subject: [PATCH] fix recover failure after unexpected exit u.ustuck and u.usteed are handled differently in 5.0.0 than in previous releases, and an unexpected halt to NetHack could result in an inability to use recover to get the game back. If the hero was engulfed, u.uswallow, could get saved to the checkpoint file with a value of 1 without a corresponding u.ustuck_mid value representing the m_id of the engulfing monster. Recover had no information to use to restore the u.ustuck pointer when loading the monsters on the level. With u.uswallow set to 1, the game would proceed to enter if-blocks based on that, and then crash/fault when it attempted to dereference u.ustuck, during the recover attempt. This updates the values of u.ustuck_mid immediately before saving struct you during a checkpoint, so that the resulting file had u.uswallow and u.ustuck_mid values that were in concert. It does the same for u.usteed and u.usteed_mid. This also now adds a save_currentstate() checkpoint call when the swallowed/unswallowed status changes, that is whenever set_ustuck() is called. --- src/do.c | 4 +++- src/mon.c | 3 +++ src/save.c | 10 ++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/do.c b/src/do.c index 54cfd5960..ea55197d7 100644 --- a/src/do.c +++ b/src/do.c @@ -1376,7 +1376,9 @@ save_currentstate(void) { NHFILE *nhfp; - if (!program_state.something_worth_saving) + if (!program_state.something_worth_saving + || program_state.in_self_recover + || program_state.in_checkpoint) return; program_state.in_checkpoint++; diff --git a/src/mon.c b/src/mon.c index c67785325..aca835286 100644 --- a/src/mon.c +++ b/src/mon.c @@ -3432,6 +3432,9 @@ set_ustuck(struct monst *mtmp) u.uswallow = 0; u.uswldtim = 0; } +#ifdef INSURANCE + save_currentstate(); +#endif } void diff --git a/src/save.c b/src/save.c index bd2696e6c..d31e23148 100644 --- a/src/save.c +++ b/src/save.c @@ -284,6 +284,13 @@ savegamestate(NHFILE *nhfp) urealtime.start_timing); Sfo_long(nhfp, &svw.wreserve, "wreserve"); Sfo_int32(nhfp, &svw.wtreserved, "wtreserved"); + /* + * It is critical to ensure that u.ustuck_mid and u.usteed_mid + * hold current and correct data, in case this is needed by + * recover. + */ + u.ustuck_mid = (u.ustuck) ? u.ustuck->m_id : 0; + u.usteed_mid = (u.usteed) ? u.usteed->m_id : 0; Sfo_you(nhfp, &u, "gamestate-you"); Sfo_char(nhfp, yyyymmddhhmmss(ubirthday), "gamestate-ubirthday", 14); Sfo_long(nhfp, &urealtime.realtime, "gamestate-realtime"); @@ -354,6 +361,9 @@ savestateinlock(void) char whynot[BUFSZ]; NHFILE *nhfp; + if (!program_state.something_worth_saving || program_state.in_self_recover) + return; + program_state.saving++; /* inhibit status and perm_invent updates */ /* When checkpointing is on, the full state needs to be written * on each checkpoint. When checkpointing is off, only the pid