/* NetHack 3.7 save.c $NHDT-Date: 1596498207 2020/08/03 23:43:27 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.160 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Michael Allison, 2009. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" #ifndef NO_SIGNAL #include #endif #if !defined(LSC) && !defined(O_WRONLY) && !defined(AZTEC_C) #include #endif #if defined(UNIX) || defined(WIN32) #define USE_BUFFERING #endif #ifdef MICRO int dotcnt, dotrow; /* also used in restore */ #endif static void FDECL(savelevchn, (NHFILE *)); static void FDECL(savedamage, (NHFILE *)); /* static void FDECL(saveobj, (NHFILE *,struct obj *)); */ /* static void FDECL(savemon, (NHFILE *,struct monst *)); */ /* static void FDECL(savelevl, (NHFILE *, BOOLEAN_P)); */ static void FDECL(saveobj, (NHFILE *,struct obj *)); static void FDECL(savemon, (NHFILE *,struct monst *)); static void FDECL(savelevl, (NHFILE *,BOOLEAN_P)); static void FDECL(saveobjchn, (NHFILE *,struct obj *)); static void FDECL(savemonchn, (NHFILE *,struct monst *)); static void FDECL(savetrapchn, (NHFILE *,struct trap *)); static void FDECL(savegamestate, (NHFILE *)); static void FDECL(save_msghistory, (NHFILE *)); #ifdef ZEROCOMP static void FDECL(zerocomp_bufon, (int)); static void FDECL(zerocomp_bufoff, (int)); static void FDECL(zerocomp_bflush, (int)); static void FDECL(zerocomp_bwrite, (int, genericptr_t, unsigned int)); static void FDECL(zerocomp_bputc, (int)); #endif #if defined(UNIX) || defined(VMS) || defined(__EMX__) || defined(WIN32) #define HUP if (!g.program_state.done_hup) #else #define HUP #endif int dosave() { if (iflags.debug_fuzzer) return 0; clear_nhwindow(WIN_MESSAGE); if (yn("Really save?") == 'n') { clear_nhwindow(WIN_MESSAGE); if (g.multi > 0) nomul(0); } else { clear_nhwindow(WIN_MESSAGE); pline("Saving..."); #if defined(UNIX) || defined(VMS) || defined(__EMX__) g.program_state.done_hup = 0; #endif if (dosave0()) { u.uhp = -1; /* universal game's over indicator */ /* make sure they see the Saving message */ display_nhwindow(WIN_MESSAGE, TRUE); exit_nhwindows("Be seeing you..."); nh_terminate(EXIT_SUCCESS); } else (void) doredraw(); } return 0; } /* returns 1 if save successful */ int dosave0() { const char *fq_save; xchar ltmp; d_level uz_save; char whynot[BUFSZ]; NHFILE *nhfp, *onhfp; /* we may get here via hangup signal, in which case we want to fix up a few of things before saving so that they won't be restored in an improper state; these will be no-ops for normal save sequence */ u.uinvulnerable = 0; if (iflags.save_uswallow) u.uswallow = 1, iflags.save_uswallow = 0; if (iflags.save_uinwater) u.uinwater = 1, iflags.save_uinwater = 0; /* bypass set_uinwater() */ if (iflags.save_uburied) u.uburied = 1, iflags.save_uburied = 0; if (!g.program_state.something_worth_saving || !g.SAVEF[0]) return 0; fq_save = fqname(g.SAVEF, SAVEPREFIX, 1); /* level files take 0 */ #if defined(UNIX) || defined(VMS) sethanguphandler((void FDECL((*), (int) )) SIG_IGN); #endif #ifndef NO_SIGNAL (void) signal(SIGINT, SIG_IGN); #endif HUP if (iflags.window_inited) { nh_uncompress(fq_save); nhfp = open_savefile(); if (nhfp) { close_nhfile(nhfp); clear_nhwindow(WIN_MESSAGE); There("seems to be an old save file."); if (yn("Overwrite the old file?") == 'n') { nh_compress(fq_save); return 0; } } } HUP mark_synch(); /* flush any buffered screen output */ nhfp = create_savefile(); if (!nhfp) { HUP pline("Cannot open save file."); (void) delete_savefile(); /* ab@unido */ return 0; } vision_recalc(2); /* shut down vision to prevent problems in the event of an impossible() call */ /* undo date-dependent luck adjustments made at startup time */ if (flags.moonphase == FULL_MOON) /* ut-sally!fletcher */ change_luck(-1); /* and unido!ab */ if (flags.friday13) change_luck(1); if (iflags.window_inited) HUP clear_nhwindow(WIN_MESSAGE); #ifdef MICRO dotcnt = 0; dotrow = 2; curs(WIN_MAP, 1, 1); if (!WINDOWPORT("X11")) putstr(WIN_MAP, 0, "Saving:"); #endif nhfp->mode = WRITING | FREEING; store_version(nhfp); store_savefileinfo(nhfp); if (nhfp && nhfp->fplog) (void) fprintf(nhfp->fplog, "# post-validation\n"); store_plname_in_file(nhfp); g.ustuck_id = (u.ustuck ? u.ustuck->m_id : 0); g.usteed_id = (u.usteed ? u.usteed->m_id : 0); savelev(nhfp, ledger_no(&u.uz)); savegamestate(nhfp); /* While copying level files around, zero out u.uz to keep * parts of the restore code from completely initializing all * in-core data structures, since all we're doing is copying. * This also avoids at least one nasty core dump. */ uz_save = u.uz; u.uz.dnum = u.uz.dlevel = 0; /* these pointers are no longer valid, and at least u.usteed * may mislead place_monster() on other levels */ set_ustuck((struct monst *) 0); u.usteed = (struct monst *) 0; for (ltmp = (xchar) 1; ltmp <= maxledgerno(); ltmp++) { if (ltmp == ledger_no(&uz_save)) continue; if (!(g.level_info[ltmp].flags & LFILE_EXISTS)) continue; #ifdef MICRO curs(WIN_MAP, 1 + dotcnt++, dotrow); if (dotcnt >= (COLNO - 1)) { dotrow++; dotcnt = 0; } if (!WINDOWPORT("X11")) { putstr(WIN_MAP, 0, "."); } mark_synch(); #endif onhfp = open_levelfile(ltmp, whynot); if (!onhfp) { HUP pline1(whynot); close_nhfile(nhfp); (void) delete_savefile(); HUP Strcpy(g.killer.name, whynot); HUP done(TRICKED); return 0; } minit(); /* ZEROCOMP */ getlev(onhfp, g.hackpid, ltmp); close_nhfile(onhfp); if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) <mp, sizeof ltmp); /* level number*/ savelev(nhfp, ltmp); /* actual level*/ delete_levelfile(ltmp); } close_nhfile(nhfp); u.uz = uz_save; /* get rid of current level --jgm */ delete_levelfile(ledger_no(&u.uz)); delete_levelfile(0); nh_compress(fq_save); /* this should probably come sooner... */ g.program_state.something_worth_saving = 0; return 1; } static void savegamestate(nhfp) NHFILE *nhfp; { unsigned long uid; struct obj * bc_objs = (struct obj *)0; uid = (unsigned long) getuid(); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &uid, sizeof uid); bwrite(nhfp->fd, (genericptr_t) &g.context, sizeof g.context); bwrite(nhfp->fd, (genericptr_t) &flags, sizeof flags); } urealtime.finish_time = getnow(); urealtime.realtime += (long) (urealtime.finish_time - urealtime.start_timing); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &u, sizeof u); bwrite(nhfp->fd, yyyymmddhhmmss(ubirthday), 14); bwrite(nhfp->fd, (genericptr_t) &urealtime.realtime, sizeof urealtime.realtime); bwrite(nhfp->fd, yyyymmddhhmmss(urealtime.start_timing), 14); } /* this is the value to use for the next update of urealtime.realtime */ urealtime.start_timing = urealtime.finish_time; save_killers(nhfp); /* must come before g.migrating_objs and g.migrating_mons are freed */ save_timers(nhfp, RANGE_GLOBAL); save_light_sources(nhfp, RANGE_GLOBAL); saveobjchn(nhfp, g.invent); /* save ball and chain if they are currently dangling free (i.e. not on floor or in inventory) */ if (CHAIN_IN_MON) { uchain->nobj = bc_objs; bc_objs = uchain; } if (BALL_IN_MON) { uball->nobj = bc_objs; bc_objs = uball; } saveobjchn(nhfp, bc_objs); saveobjchn(nhfp, g.migrating_objs); savemonchn(nhfp, g.migrating_mons); if (release_data(nhfp)) { g.invent = 0; g.migrating_objs = 0; g.migrating_mons = 0; } if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) g.mvitals, sizeof g.mvitals); save_dungeon(nhfp, (boolean) !!perform_bwrite(nhfp), (boolean) !!release_data(nhfp)); savelevchn(nhfp); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &g.moves, sizeof g.moves); bwrite(nhfp->fd, (genericptr_t) &g.monstermoves, sizeof g.monstermoves); bwrite(nhfp->fd, (genericptr_t) &g.quest_status, sizeof g.quest_status); bwrite(nhfp->fd, (genericptr_t) g.spl_book, sizeof (struct spell) * (MAXSPELL + 1)); } save_artifacts(nhfp); save_oracles(nhfp); if (g.ustuck_id) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &g.ustuck_id, sizeof g.ustuck_id); } if (g.usteed_id) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &g.usteed_id, sizeof g.usteed_id); } if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) g.pl_character, sizeof g.pl_character); bwrite(nhfp->fd, (genericptr_t) g.pl_fruit, sizeof g.pl_fruit); } savefruitchn(nhfp); savenames(nhfp); save_waterlevel(nhfp); save_msghistory(nhfp); if (nhfp->structlevel) bflush(nhfp->fd); } boolean tricked_fileremoved(nhfp, whynot) NHFILE *nhfp; char *whynot; { if (!nhfp) { pline1(whynot); pline("Probably someone removed it."); Strcpy(g.killer.name, whynot); done(TRICKED); return TRUE; } return FALSE; } #ifdef INSURANCE void savestateinlock() { int hpid = 0; char whynot[BUFSZ]; NHFILE *nhfp; /* When checkpointing is on, the full state needs to be written * on each checkpoint. When checkpointing is off, only the pid * needs to be in the level.0 file, so it does not need to be * constantly rewritten. When checkpointing is turned off during * a game, however, the file has to be rewritten once to truncate * it and avoid restoring from outdated information. * * Restricting g.havestate to this routine means that an additional * noop pid rewriting will take place on the first "checkpoint" after * the game is started or restored, if checkpointing is off. */ if (flags.ins_chkpt || g.havestate) { /* save the rest of the current game state in the lock file, * following the original int pid, the current level number, * and the current savefile name, which should not be subject * to any internal compression schemes since they must be * readable by an external utility */ nhfp = open_levelfile(0, whynot); if (tricked_fileremoved(nhfp, whynot)) return; if (nhfp->structlevel) (void) read(nhfp->fd, (genericptr_t) &hpid, sizeof hpid); if (g.hackpid != hpid) { Sprintf(whynot, "Level #0 pid (%d) doesn't match ours (%d)!", hpid, g.hackpid); pline1(whynot); Strcpy(g.killer.name, whynot); done(TRICKED); } close_nhfile(nhfp); nhfp = create_levelfile(0, whynot); if (!nhfp) { pline1(whynot); Strcpy(g.killer.name, whynot); done(TRICKED); return; } nhfp->mode = WRITING; if (nhfp->structlevel) (void) write(nhfp->fd, (genericptr_t) &g.hackpid, sizeof g.hackpid); if (flags.ins_chkpt) { int currlev = ledger_no(&u.uz); if (nhfp->structlevel) (void) write(nhfp->fd, (genericptr_t) &currlev, sizeof currlev); save_savefile_name(nhfp); store_version(nhfp); store_savefileinfo(nhfp); store_plname_in_file(nhfp); g.ustuck_id = (u.ustuck ? u.ustuck->m_id : 0); g.usteed_id = (u.usteed ? u.usteed->m_id : 0); savegamestate(nhfp); } close_nhfile(nhfp); } g.havestate = flags.ins_chkpt; } #endif void savelev(nhfp, lev) NHFILE *nhfp; xchar lev; { #ifdef TOS short tlev; #endif /* * Level file contents: * version info (handled by caller); * save file info (compression type; also by caller); * process ID; * internal level number (ledger number); * bones info; * actual level data. * * If we're tearing down the current level without saving anything * (which happens at end of game or upon entrance to endgame or * after an aborted restore attempt) then we don't want to do any * actual I/O. So when only freeing, we skip to the bones info * portion (which has some freeing to do), then jump quite a bit * further ahead to the middle of the 'actual level data' portion. */ if (nhfp->mode != FREEING) { /* WRITING (probably ORed with FREEING), or COUNTING */ /* purge any dead monsters (necessary if we're starting a panic save rather than a normal one, or sometimes when changing levels without taking time -- e.g. create statue trap then immediately level teleport) */ if (iflags.purge_monsters) dmonsfree(); if (!nhfp) panic("Save on bad file!"); /* impossible */ if (lev >= 0 && lev <= maxledgerno()) g.level_info[lev].flags |= VISITED; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &g.hackpid, sizeof g.hackpid); #ifdef TOS tlev = lev; tlev &= 0x00ff; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &tlev, sizeof tlev); #else if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &lev, sizeof lev); #endif } /* bones info comes before level data; the intent is for an external program ('hearse') to be able to match a bones file with the corresponding log file entry--or perhaps just skip that?--without the guessing that was needed in 3.4.3 and without having to interpret level data to find where to start; unfortunately it still needs to handle all the data compression schemes */ savecemetery(nhfp, &g.level.bonesinfo); if (nhfp->mode == FREEING) /* see above */ goto skip_lots; savelevl(nhfp, (boolean) ((sfsaveinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP)); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) g.lastseentyp, sizeof g.lastseentyp); bwrite(nhfp->fd, (genericptr_t) &g.monstermoves, sizeof g.monstermoves); bwrite(nhfp->fd, (genericptr_t) &g.upstair, sizeof (stairway)); bwrite(nhfp->fd, (genericptr_t) &g.dnstair, sizeof (stairway)); bwrite(nhfp->fd, (genericptr_t) &g.upladder, sizeof (stairway)); bwrite(nhfp->fd, (genericptr_t) &g.dnladder, sizeof (stairway)); bwrite(nhfp->fd, (genericptr_t) &g.sstairs, sizeof (stairway)); bwrite(nhfp->fd, (genericptr_t) &g.updest, sizeof (dest_area)); bwrite(nhfp->fd, (genericptr_t) &g.dndest, sizeof (dest_area)); bwrite(nhfp->fd, (genericptr_t) &g.level.flags, sizeof g.level.flags); bwrite(nhfp->fd, (genericptr_t) g.doors, sizeof g.doors); } save_rooms(nhfp); /* no dynamic memory to reclaim */ /* from here on out, saving also involves allocated memory cleanup */ skip_lots: /* timers and lights must be saved before monsters and objects */ save_timers(nhfp, RANGE_LEVEL); save_light_sources(nhfp, RANGE_LEVEL); savemonchn(nhfp, fmon); save_worm(nhfp); /* save worm information */ savetrapchn(nhfp, g.ftrap); saveobjchn(nhfp, fobj); saveobjchn(nhfp, g.level.buriedobjlist); saveobjchn(nhfp, g.billobjs); if (release_data(nhfp)) { int x,y; /* TODO: maybe use clear_level_structures() */ for (y = 0; y < ROWNO; y++) for (x = 0; x < COLNO; x++) { g.level.monsters[x][y] = 0; g.level.objects[x][y] = 0; levl[x][y].seenv = 0; levl[x][y].glyph = GLYPH_UNEXPLORED; } fmon = 0; g.ftrap = 0; fobj = 0; g.level.buriedobjlist = 0; g.billobjs = 0; /* level.bonesinfo = 0; -- handled by savecemetery() */ } save_engravings(nhfp); savedamage(nhfp); /* pending shop wall and/or floor repair */ save_regions(nhfp); if (nhfp->mode != FREEING) { if (nhfp->structlevel) bflush(nhfp->fd); } } static void savelevl(nhfp, rlecomp) NHFILE *nhfp; boolean rlecomp; { #ifdef RLECOMP struct rm *prm, *rgrm; int x, y; uchar match; if (rlecomp) { /* perform run-length encoding of rm structs */ rgrm = &levl[0][0]; /* start matching at first rm */ match = 0; for (y = 0; y < ROWNO; y++) { for (x = 0; x < COLNO; x++) { prm = &levl[x][y]; if (prm->glyph == rgrm->glyph && prm->typ == rgrm->typ && prm->seenv == rgrm->seenv && prm->horizontal == rgrm->horizontal && prm->flags == rgrm->flags && prm->lit == rgrm->lit && prm->waslit == rgrm->waslit && prm->roomno == rgrm->roomno && prm->edge == rgrm->edge && prm->candig == rgrm->candig) { match++; if (match > 254) { match = 254; /* undo this match */ goto writeout; } } else { /* run has been broken, write out run-length encoding */ writeout: if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &match, sizeof match); bwrite(nhfp->fd, (genericptr_t) rgrm, sizeof *rgrm); } /* start encoding again. we have at least 1 rm in the next run, viz. this one. */ match = 1; rgrm = prm; } } } if (match > 0) { if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &match, sizeof (uchar)); bwrite(nhfp->fd, (genericptr_t) rgrm, sizeof (struct rm)); } } return; } #else /* !RLECOMP */ nhUse(rlecomp); #endif /* ?RLECOMP */ if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) levl, sizeof levl); } } /* used when saving a level and also when saving dungeon overview data */ void savecemetery(nhfp, cemeteryaddr) NHFILE *nhfp; struct cemetery **cemeteryaddr; { struct cemetery *thisbones, *nextbones; int flag; flag = *cemeteryaddr ? 0 : -1; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &flag, sizeof flag); } nextbones = *cemeteryaddr; while ((thisbones = nextbones) != 0) { nextbones = thisbones->next; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) thisbones, sizeof *thisbones); } if (release_data(nhfp)) free((genericptr_t) thisbones); } if (release_data(nhfp)) *cemeteryaddr = 0; } static void savedamage(nhfp) NHFILE *nhfp; { register struct damage *damageptr, *tmp_dam; unsigned int xl = 0; damageptr = g.level.damagelist; for (tmp_dam = damageptr; tmp_dam; tmp_dam = tmp_dam->next) xl++; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &xl, sizeof xl); } while (xl--) { if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) damageptr, sizeof *damageptr); } tmp_dam = damageptr; damageptr = damageptr->next; if (release_data(nhfp)) free((genericptr_t) tmp_dam); } if (release_data(nhfp)) g.level.damagelist = 0; } static void saveobj(nhfp, otmp) NHFILE *nhfp; struct obj *otmp; { int buflen, zerobuf = 0; buflen = (int) sizeof (struct obj); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); bwrite(nhfp->fd, (genericptr_t) otmp, buflen); } if (otmp->oextra) { buflen = ONAME(otmp) ? (int) strlen(ONAME(otmp)) + 1 : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) ONAME(otmp), buflen); } /* defer to savemon() for this one */ if (OMONST(otmp)) { savemon(nhfp, OMONST(otmp)); } else { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &zerobuf, sizeof zerobuf); } /* extra info about scroll of mail */ buflen = OMAILCMD(otmp) ? (int) strlen(OMAILCMD(otmp)) + 1 : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) OMAILCMD(otmp), buflen); } /* omid used to be indirect via a pointer in oextra but has become part of oextra itself; 0 means not applicable and gets saved/restored whenever any other oxtra components do */ if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &OMID(otmp), sizeof OMID(otmp)); } } static void saveobjchn(nhfp, otmp) NHFILE *nhfp; register struct obj *otmp; { register struct obj *otmp2; int minusone = -1; while (otmp) { otmp2 = otmp->nobj; if (perform_bwrite(nhfp)) { saveobj(nhfp, otmp); } if (Has_contents(otmp)) saveobjchn(nhfp, otmp->cobj); if (release_data(nhfp)) { /* * If these are on the floor, the discarding could be * due to game save, or we could just be changing levels. * Always invalidate the pointer, but ensure that we have * the o_id in order to restore the pointer on reload. */ if (otmp == g.context.victual.piece) { g.context.victual.o_id = otmp->o_id; g.context.victual.piece = (struct obj *) 0; } if (otmp == g.context.tin.tin) { g.context.tin.o_id = otmp->o_id; g.context.tin.tin = (struct obj *) 0; } if (otmp == g.context.spbook.book) { g.context.spbook.o_id = otmp->o_id; g.context.spbook.book = (struct obj *) 0; } otmp->where = OBJ_FREE; /* set to free so dealloc will work */ otmp->nobj = NULL; /* nobj saved into otmp2 */ otmp->cobj = NULL; /* contents handled above */ otmp->timed = 0; /* not timed any more */ otmp->lamplit = 0; /* caller handled lights */ dealloc_obj(otmp); } otmp = otmp2; } if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &minusone, sizeof (int)); } } static void savemon(nhfp, mtmp) NHFILE *nhfp; struct monst *mtmp; { int buflen; mtmp->mtemplit = 0; /* normally clear; if set here then a panic save * is being written while bhit() was executing */ buflen = (int) sizeof (struct monst); if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); bwrite(nhfp->fd, (genericptr_t) mtmp, buflen); } if (mtmp->mextra) { buflen = MNAME(mtmp) ? (int) strlen(MNAME(mtmp)) + 1 : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) MNAME(mtmp), buflen); } buflen = EGD(mtmp) ? (int) sizeof (struct egd) : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) EGD(mtmp), buflen); } buflen = EPRI(mtmp) ? (int) sizeof (struct epri) : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof buflen); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) EPRI(mtmp), buflen); } buflen = ESHK(mtmp) ? (int) sizeof (struct eshk) : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof (int)); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) ESHK(mtmp), buflen); } buflen = EMIN(mtmp) ? (int) sizeof (struct emin) : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof (int)); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) EMIN(mtmp), buflen); } buflen = EDOG(mtmp) ? (int) sizeof (struct edog) : 0; if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof (int)); if (buflen > 0) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) EDOG(mtmp), buflen); } /* mcorpsenm is inline int rather than pointer to something, so doesn't need to be preceded by a length field */ if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &MCORPSENM(mtmp), sizeof MCORPSENM(mtmp)); } } static void savemonchn(nhfp, mtmp) NHFILE *nhfp; register struct monst *mtmp; { register struct monst *mtmp2; int minusone = -1; while (mtmp) { mtmp2 = mtmp->nmon; if (perform_bwrite(nhfp)) { mtmp->mnum = monsndx(mtmp->data); if (mtmp->ispriest) forget_temple_entry(mtmp); /* EPRI() */ savemon(nhfp, mtmp); } if (mtmp->minvent) saveobjchn(nhfp, mtmp->minvent); if (release_data(nhfp)) { if (mtmp == g.context.polearm.hitmon) { g.context.polearm.m_id = mtmp->m_id; g.context.polearm.hitmon = NULL; } mtmp->nmon = NULL; /* nmon saved into mtmp2 */ dealloc_monst(mtmp); } mtmp = mtmp2; } if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &minusone, sizeof (int)); } } /* save traps; g.ftrap is the only trap chain so the 2nd arg is superfluous */ static void savetrapchn(nhfp, trap) NHFILE *nhfp; register struct trap *trap; { static struct trap zerotrap; register struct trap *trap2; while (trap) { trap2 = trap->ntrap; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) trap, sizeof *trap); } if (release_data(nhfp)) dealloc_trap(trap); trap = trap2; } if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &zerotrap, sizeof zerotrap); } } /* save all the fruit names and ID's; this is used only in saving whole games * (not levels) and in saving bones levels. When saving a bones level, * we only want to save the fruits which exist on the bones level; the bones * level routine marks nonexistent fruits by making the fid negative. */ void savefruitchn(nhfp) NHFILE *nhfp; { static struct fruit zerofruit; register struct fruit *f2, *f1; f1 = g.ffruit; while (f1) { f2 = f1->nextf; if (f1->fid >= 0 && perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) f1, sizeof *f1); } if (release_data(nhfp)) dealloc_fruit(f1); f1 = f2; } if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &zerofruit, sizeof zerofruit); } if (release_data(nhfp)) g.ffruit = 0; } static void savelevchn(nhfp) NHFILE *nhfp; { s_level *tmplev, *tmplev2; int cnt = 0; for (tmplev = g.sp_levchn; tmplev; tmplev = tmplev->next) cnt++; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &cnt, sizeof cnt); } for (tmplev = g.sp_levchn; tmplev; tmplev = tmplev2) { tmplev2 = tmplev->next; if (perform_bwrite(nhfp)) { if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) tmplev, sizeof *tmplev); } if (release_data(nhfp)) free((genericptr_t) tmplev); } if (release_data(nhfp)) g.sp_levchn = 0; } void store_plname_in_file(nhfp) NHFILE *nhfp; { int plsiztmp = PL_NSIZ; if (nhfp->structlevel) { bufoff(nhfp->fd); /* bwrite() before bufon() uses plain write() */ bwrite(nhfp->fd, (genericptr_t) &plsiztmp, sizeof plsiztmp); bwrite(nhfp->fd, (genericptr_t) g.plname, plsiztmp); bufon(nhfp->fd); } return; } static void save_msghistory(nhfp) NHFILE *nhfp; { char *msg; int msgcount = 0, msglen; int minusone = -1; boolean init = TRUE; if (perform_bwrite(nhfp)) { /* ask window port for each message in sequence */ while ((msg = getmsghistory(init)) != 0) { init = FALSE; msglen = strlen(msg); if (msglen < 1) continue; /* sanity: truncate if necessary (shouldn't happen); no need to modify msg[] since terminator isn't written */ if (msglen > BUFSZ - 1) msglen = BUFSZ - 1; if (nhfp->structlevel) { bwrite(nhfp->fd, (genericptr_t) &msglen, sizeof msglen); bwrite(nhfp->fd, (genericptr_t) msg, msglen); } ++msgcount; } if (nhfp->structlevel) bwrite(nhfp->fd, (genericptr_t) &minusone, sizeof (int)); } debugpline1("Stored %d messages into savefile.", msgcount); /* note: we don't attempt to handle release_data() here */ } void store_savefileinfo(nhfp) NHFILE *nhfp; { /* sfcap (decl.c) describes the savefile feature capabilities * that are supported by this port/platform build. * * sfsaveinfo (decl.c) describes the savefile info that actually * gets written into the savefile, and is used to determine the * save file being written. * * sfrestinfo (decl.c) describes the savefile info that is * being used to read the information from an existing savefile. */ if (nhfp->structlevel) { bufoff(nhfp->fd); /* bwrite() before bufon() uses plain write() */ bwrite(nhfp->fd, (genericptr_t) &sfsaveinfo, (unsigned) sizeof sfsaveinfo); bufon(nhfp->fd); } return; } /* also called by prscore(); this probably belongs in dungeon.c... */ void free_dungeons() { #ifdef FREE_ALL_MEMORY NHFILE tnhfp; zero_nhfile(&tnhfp); /* also sets fd to -1 */ tnhfp.mode = FREEING; savelevchn(&tnhfp); save_dungeon(&tnhfp, FALSE, TRUE); free_luathemes(TRUE); #endif return; } void freedynamicdata() { NHFILE tnhfp; #if defined(UNIX) && defined(MAIL) free_maildata(); #endif zero_nhfile(&tnhfp); /* also sets fd to -1 */ tnhfp.mode = FREEING; free_menu_coloring(); free_invbuf(); /* let_to_name (invent.c) */ free_youbuf(); /* You_buf,&c (pline.c) */ msgtype_free(); tmp_at(DISP_FREEMEM, 0); /* temporary display effects */ #ifdef FREE_ALL_MEMORY #define free_current_level() savelev(&tnhfp, -1) #define freeobjchn(X) (saveobjchn(&tnhfp, X), X = 0) #define freemonchn(X) (savemonchn(&tnhfp, X), X = 0) #define freefruitchn() savefruitchn(&tnhfp) #define freenames() savenames(&tnhfp) #define free_killers() save_killers(&tnhfp) #define free_oracles() save_oracles(&tnhfp) #define free_waterlevel() save_waterlevel(&tnhfp) #define free_timers(R) save_timers(&tnhfp, R) #define free_light_sources(R) save_light_sources(&tnhfp, R) #define free_animals() mon_animal_list(FALSE) /* move-specific data */ dmonsfree(); /* release dead monsters */ /* level-specific data */ free_current_level(); /* game-state data [ought to reorganize savegamestate() to handle this] */ free_killers(); free_timers(RANGE_GLOBAL); free_light_sources(RANGE_GLOBAL); freeobjchn(g.invent); freeobjchn(g.migrating_objs); freemonchn(g.migrating_mons); freemonchn(g.mydogs); /* ascension or dungeon escape */ /* freelevchn(); -- [folded into free_dungeons()] */ free_animals(); free_oracles(); freefruitchn(); freenames(); free_waterlevel(); free_dungeons(); /* some pointers in iflags */ if (iflags.wc_font_map) free((genericptr_t) iflags.wc_font_map), iflags.wc_font_map = 0; if (iflags.wc_font_message) free((genericptr_t) iflags.wc_font_message), iflags.wc_font_message = 0; if (iflags.wc_font_text) free((genericptr_t) iflags.wc_font_text), iflags.wc_font_text = 0; if (iflags.wc_font_menu) free((genericptr_t) iflags.wc_font_menu), iflags.wc_font_menu = 0; if (iflags.wc_font_status) free((genericptr_t) iflags.wc_font_status), iflags.wc_font_status = 0; if (iflags.wc_tile_file) free((genericptr_t) iflags.wc_tile_file), iflags.wc_tile_file = 0; free_autopickup_exceptions(); /* miscellaneous */ /* free_pickinv_cache(); -- now done from really_done()... */ free_symsets(); #ifdef USER_SOUNDS release_sound_mappings(); #endif #endif /* FREE_ALL_MEMORY */ if (VIA_WINDOWPORT()) status_finish(); #ifdef DUMPLOG dumplogfreemessages(); #endif /* last, because it frees data that might be used by panic() to provide feedback to the user; conceivably other freeing might trigger panic */ sysopt_release(); /* SYSCF strings */ return; } /*save.c*/