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.
This commit is contained in:
nhmall
2026-06-07 01:31:19 -04:00
parent 1f4198c705
commit 51486d0c02
3 changed files with 16 additions and 1 deletions
+3 -1
View File
@@ -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++;
+3
View File
@@ -3432,6 +3432,9 @@ set_ustuck(struct monst *mtmp)
u.uswallow = 0;
u.uswldtim = 0;
}
#ifdef INSURANCE
save_currentstate();
#endif
}
void
+10
View File
@@ -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