Files
nethack/src/save.c
Sean Hunt 907a2dfee0 Fix a memory leak on termination.
Found by Alex Smith.
2015-03-25 14:34:28 -04:00

1455 lines
37 KiB
C

/* NetHack 3.5 save.c $NHDT-Date: 1426496455 2015/03/16 09:00:55 $ $NHDT-Branch: master $:$NHDT-Revision: 1.62 $ */
/* NetHack 3.5 save.c $Date: 2012/02/16 02:40:24 $ $Revision: 1.53 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
#include "hack.h"
#include "lev.h"
#ifndef NO_SIGNAL
#include <signal.h>
#endif
#if !defined(LSC) && !defined(O_WRONLY) && !defined(AZTEC_C)
#include <fcntl.h>
#endif
#ifdef MFLOPPY
long bytes_counted;
static int count_only;
#endif
#ifdef MICRO
int dotcnt, dotrow; /* also used in restore */
#endif
STATIC_DCL void FDECL(savelevchn, (int,int));
STATIC_DCL void FDECL(savedamage, (int,int));
STATIC_DCL void FDECL(saveobj, (int,struct obj *));
STATIC_DCL void FDECL(saveobjchn, (int,struct obj *,int));
STATIC_DCL void FDECL(savemon, (int,struct monst *));
STATIC_DCL void FDECL(savemonchn, (int,struct monst *,int));
STATIC_DCL void FDECL(savetrapchn, (int,struct trap *,int));
STATIC_DCL void FDECL(savegamestate, (int,int));
STATIC_OVL void FDECL(save_msghistory, (int,int));
#ifdef MFLOPPY
STATIC_DCL void FDECL(savelev0, (int,XCHAR_P,int));
STATIC_DCL boolean NDECL(swapout_oldest);
STATIC_DCL void FDECL(copyfile, (char *,char *));
#endif /* MFLOPPY */
STATIC_DCL void FDECL(savelevl, (int fd, BOOLEAN_P));
STATIC_DCL void FDECL(def_bufon, (int));
STATIC_DCL void FDECL(def_bufoff, (int));
STATIC_DCL void FDECL(def_bflush, (int));
STATIC_DCL void FDECL(def_bwrite, (int,genericptr_t,unsigned int));
#ifdef ZEROCOMP
STATIC_DCL void FDECL(zerocomp_bufon, (int));
STATIC_DCL void FDECL(zerocomp_bufoff, (int));
STATIC_DCL void FDECL(zerocomp_bflush, (int));
STATIC_DCL void FDECL(zerocomp_bwrite, (int,genericptr_t,unsigned int));
STATIC_DCL void FDECL(zerocomp_bputc, (int));
#endif
static struct save_procs {
const char *name;
void FDECL((*save_bufon), (int));
void FDECL((*save_bufoff), (int));
void FDECL((*save_bflush), (int));
void FDECL((*save_bwrite), (int,genericptr_t,unsigned int));
void FDECL((*save_bclose), (int));
} saveprocs = {
#if !defined(ZEROCOMP) || (defined(COMPRESS) || defined(ZLIB_COMP))
"externalcomp",
def_bufon,
def_bufoff,
def_bflush,
def_bwrite,
def_bclose,
#else
"zerocomp",
zerocomp_bufon,
zerocomp_bufoff,
zerocomp_bflush,
zerocomp_bwrite,
zerocomp_bclose,
#endif
};
static long nulls[sizeof(struct trap) + sizeof(struct fruit)];
#if defined(UNIX) || defined(VMS) || defined(__EMX__) || defined(WIN32)
#define HUP if (!program_state.done_hup)
#else
#define HUP
#endif
/* compute object glyphs for vanilla nethack -- statue patch */
void
make_glyphs_vanilla(lev)
xchar lev;
{
int x,y;
for (x = 0; x < COLNO; x++)
for (y = 0; y < ROWNO; y++)
if ( level.objects[x][y] != 0 &&
level.objects[x][y]->otyp == STATUE &&
glyph_is_statue(levl[x][y].glyph))
levl[x][y].glyph = obj_to_glyph_vanilla(level.objects[x][y]);
}
/* need to preserve these during save to avoid accessing freed memory */
static unsigned ustuck_id = 0, usteed_id = 0;
int
dosave()
{
clear_nhwindow(WIN_MESSAGE);
if(yn("Really save?") == 'n') {
clear_nhwindow(WIN_MESSAGE);
if(multi > 0) nomul(0);
} else {
clear_nhwindow(WIN_MESSAGE);
pline("Saving...");
#if defined(UNIX) || defined(VMS) || defined(__EMX__)
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...");
terminate(EXIT_SUCCESS);
} else (void)doredraw();
}
return 0;
}
/* returns 1 if save successful */
int
dosave0()
{
const char *fq_save;
register int fd, ofd;
xchar ltmp;
d_level uz_save;
char whynot[BUFSZ];
/* 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_uinwater) u.uinwater = 1, iflags.save_uinwater = 0;
if (iflags.save_uburied) u.uburied = 1, iflags.save_uburied = 0;
if (!program_state.something_worth_saving || !SAVEF[0])
return 0;
fq_save = fqname(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
#if defined(MICRO) && defined(MFLOPPY)
if (!saveDiskPrompt(0)) return 0;
#endif
HUP if (iflags.window_inited) {
nh_uncompress(fq_save);
fd = open_savefile();
if (fd > 0) {
(void) close(fd);
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 */
fd = create_savefile();
if(fd < 0) {
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 (strncmpi("X11", windowprocs.name, 3))
putstr(WIN_MAP, 0, "Saving:");
#endif
#ifdef MFLOPPY
/* make sure there is enough disk space */
if (iflags.checkspace) {
long fds, needed;
savelev(fd, ledger_no(&u.uz), COUNT_SAVE);
savegamestate(fd, COUNT_SAVE);
needed = bytes_counted;
for (ltmp = 1; ltmp <= maxledgerno(); ltmp++)
if (ltmp != ledger_no(&u.uz) && level_info[ltmp].where)
needed += level_info[ltmp].size + (sizeof ltmp);
fds = freediskspace(fq_save);
if (needed > fds) {
HUP {
There("is insufficient space on SAVE disk.");
pline("Require %ld bytes but only have %ld.", needed, fds);
}
flushout();
(void) close(fd);
(void) delete_savefile();
return 0;
}
co_false();
}
#endif /* MFLOPPY */
store_version(fd);
store_savefileinfo(fd);
store_plname_in_file(fd);
ustuck_id = (u.ustuck ? u.ustuck->m_id : 0);
usteed_id = (u.usteed ? u.usteed->m_id : 0);
savelev(fd, ledger_no(&u.uz), WRITE_SAVE | FREE_SAVE);
savegamestate(fd, WRITE_SAVE | FREE_SAVE);
/* 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
*/
u.ustuck = (struct monst *)0;
u.usteed = (struct monst *)0;
for(ltmp = (xchar)1; ltmp <= maxledgerno(); ltmp++) {
if (ltmp == ledger_no(&uz_save)) continue;
if (!(level_info[ltmp].flags & LFILE_EXISTS)) continue;
#ifdef MICRO
curs(WIN_MAP, 1 + dotcnt++, dotrow);
if (dotcnt >= (COLNO - 1)) {
dotrow++;
dotcnt = 0;
}
if (strncmpi("X11", windowprocs.name, 3)){
putstr(WIN_MAP, 0, ".");
}
mark_synch();
#endif
ofd = open_levelfile(ltmp, whynot);
if (ofd < 0) {
HUP pline1(whynot);
(void) close(fd);
(void) delete_savefile();
HUP Strcpy(killer.name, whynot);
HUP done(TRICKED);
return(0);
}
minit(); /* ZEROCOMP */
getlev(ofd, hackpid, ltmp, FALSE);
(void) close(ofd);
bwrite(fd, (genericptr_t) &ltmp, sizeof ltmp); /* level number*/
savelev(fd, ltmp, WRITE_SAVE | FREE_SAVE); /* actual level*/
delete_levelfile(ltmp);
}
bclose(fd);
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... */
program_state.something_worth_saving = 0;
return(1);
}
STATIC_OVL void
savegamestate(fd, mode)
register int fd, mode;
{
unsigned long uid;
#ifdef MFLOPPY
count_only = (mode & COUNT_SAVE);
#endif
uid = (unsigned long)getuid();
bwrite(fd, (genericptr_t) &uid, sizeof uid);
bwrite(fd, (genericptr_t) &context, sizeof(struct context_info));
bwrite(fd, (genericptr_t) &flags, sizeof(struct flag));
#ifdef SYSFLAGS
bwrite(fd, (genericptr_t) &sysflags, sizeof(struct sysflag));
#endif
urealtime.realtime += (getnow() - urealtime.restored);
bwrite(fd, (genericptr_t) &u, sizeof(struct you));
bwrite(fd, yyyymmddhhmmss(ubirthday), 14);
bwrite(fd, yyyymmddhhmmss(urealtime.realtime), 14);
bwrite(fd, yyyymmddhhmmss(urealtime.restored), 14);
save_killers(fd, mode);
/* must come before migrating_objs and migrating_mons are freed */
save_timers(fd, mode, RANGE_GLOBAL);
save_light_sources(fd, mode, RANGE_GLOBAL);
saveobjchn(fd, invent, mode);
if (BALL_IN_MON) {
/* prevent loss of ball & chain when swallowed */
uball->nobj = uchain;
uchain->nobj = (struct obj *)0;
saveobjchn(fd, uball, mode);
} else {
saveobjchn(fd, (struct obj *)0, mode);
}
saveobjchn(fd, migrating_objs, mode);
savemonchn(fd, migrating_mons, mode);
if (release_data(mode)) {
invent = 0;
migrating_objs = 0;
migrating_mons = 0;
}
bwrite(fd, (genericptr_t) mvitals, sizeof(mvitals));
save_dungeon(fd, (boolean)!!perform_bwrite(mode),
(boolean)!!release_data(mode));
savelevchn(fd, mode);
bwrite(fd, (genericptr_t) &moves, sizeof moves);
bwrite(fd, (genericptr_t) &monstermoves, sizeof monstermoves);
bwrite(fd, (genericptr_t) &quest_status, sizeof(struct q_score));
bwrite(fd, (genericptr_t) spl_book,
sizeof(struct spell) * (MAXSPELL + 1));
save_artifacts(fd);
save_oracles(fd, mode);
if(ustuck_id)
bwrite(fd, (genericptr_t) &ustuck_id, sizeof ustuck_id);
if(usteed_id)
bwrite(fd, (genericptr_t) &usteed_id, sizeof usteed_id);
bwrite(fd, (genericptr_t) pl_character, sizeof pl_character);
bwrite(fd, (genericptr_t) pl_fruit, sizeof pl_fruit);
savefruitchn(fd, mode);
savenames(fd, mode);
save_waterlevel(fd, mode);
save_msghistory(fd, mode);
bflush(fd);
}
#ifdef INSURANCE
void
savestateinlock()
{
int fd, hpid;
static boolean havestate = TRUE;
char whynot[BUFSZ];
/* 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 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 || 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
*/
fd = open_levelfile(0, whynot);
if (fd < 0) {
pline1(whynot);
pline("Probably someone removed it.");
Strcpy(killer.name, whynot);
done(TRICKED);
return;
}
(void) read(fd, (genericptr_t) &hpid, sizeof(hpid));
if (hackpid != hpid) {
Sprintf(whynot,
"Level #0 pid (%d) doesn't match ours (%d)!",
hpid, hackpid);
pline1(whynot);
Strcpy(killer.name, whynot);
done(TRICKED);
}
(void) close(fd);
fd = create_levelfile(0, whynot);
if (fd < 0) {
pline1(whynot);
Strcpy(killer.name, whynot);
done(TRICKED);
return;
}
(void) write(fd, (genericptr_t) &hackpid, sizeof(hackpid));
if (flags.ins_chkpt) {
int currlev = ledger_no(&u.uz);
(void) write(fd, (genericptr_t) &currlev, sizeof(currlev));
save_savefile_name(fd);
store_version(fd);
store_savefileinfo(fd);
store_plname_in_file(fd);
ustuck_id = (u.ustuck ? u.ustuck->m_id : 0);
usteed_id = (u.usteed ? u.usteed->m_id : 0);
savegamestate(fd, WRITE_SAVE);
}
bclose(fd);
}
havestate = flags.ins_chkpt;
}
#endif
#ifdef MFLOPPY
boolean
savelev(fd, lev, mode)
int fd;
xchar lev;
int mode;
{
if (mode & COUNT_SAVE) {
bytes_counted = 0;
savelev0(fd, lev, COUNT_SAVE);
/* probably bytes_counted will be filled in again by an
* immediately following WRITE_SAVE anyway, but we'll
* leave it out of checkspace just in case */
if (iflags.checkspace) {
while (bytes_counted > freediskspace(levels))
if (!swapout_oldest())
return FALSE;
}
}
if (mode & (WRITE_SAVE | FREE_SAVE)) {
bytes_counted = 0;
savelev0(fd, lev, mode);
}
if (mode != FREE_SAVE) {
level_info[lev].where = ACTIVE;
level_info[lev].time = moves;
level_info[lev].size = bytes_counted;
}
return TRUE;
}
STATIC_OVL void
savelev0(fd,lev,mode)
#else
void
savelev(fd,lev,mode)
#endif
int fd;
xchar lev;
int mode;
{
#ifdef TOS
short tlev;
#endif
/* make remembered glyphs correct fo rloading into vanilla nh */
make_glyphs_vanilla(lev);
/* if we're tearing down the current level without saving anything
(which happens upon entrance to the endgame or after an aborted
restore attempt) then we don't want to do any actual I/O */
if (mode == FREE_SAVE) goto skip_lots;
if (iflags.purge_monsters) {
/* 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) */
dmonsfree();
}
if(fd < 0) panic("Save on bad file!"); /* impossible */
#ifdef MFLOPPY
count_only = (mode & COUNT_SAVE);
#endif
if (lev >= 0 && lev <= maxledgerno())
level_info[lev].flags |= VISITED;
bwrite(fd,(genericptr_t) &hackpid,sizeof(hackpid));
#ifdef TOS
tlev=lev; tlev &= 0x00ff;
bwrite(fd,(genericptr_t) &tlev,sizeof(tlev));
#else
bwrite(fd,(genericptr_t) &lev,sizeof(lev));
#endif
savecemetery(fd, mode, &level.bonesinfo);
savelevl(fd, (boolean)((sfsaveinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP));
bwrite(fd,(genericptr_t) lastseentyp,sizeof(lastseentyp));
bwrite(fd,(genericptr_t) &monstermoves,sizeof(monstermoves));
bwrite(fd,(genericptr_t) &upstair,sizeof(stairway));
bwrite(fd,(genericptr_t) &dnstair,sizeof(stairway));
bwrite(fd,(genericptr_t) &upladder,sizeof(stairway));
bwrite(fd,(genericptr_t) &dnladder,sizeof(stairway));
bwrite(fd,(genericptr_t) &sstairs,sizeof(stairway));
bwrite(fd,(genericptr_t) &updest,sizeof(dest_area));
bwrite(fd,(genericptr_t) &dndest,sizeof(dest_area));
bwrite(fd,(genericptr_t) &level.flags,sizeof(level.flags));
bwrite(fd, (genericptr_t) doors, sizeof(doors));
save_rooms(fd); /* no dynamic memory to reclaim */
/* from here on out, saving also involves allocated memory cleanup */
skip_lots:
/* this comes before the map, so need cleanup here if we skipped */
if (mode == FREE_SAVE) savecemetery(fd, mode, &level.bonesinfo);
/* must be saved before mons, objs, and buried objs */
save_timers(fd, mode, RANGE_LEVEL);
save_light_sources(fd, mode, RANGE_LEVEL);
savemonchn(fd, fmon, mode);
save_worm(fd, mode); /* save worm information */
savetrapchn(fd, ftrap, mode);
saveobjchn(fd, fobj, mode);
saveobjchn(fd, level.buriedobjlist, mode);
saveobjchn(fd, billobjs, mode);
if (release_data(mode)) {
fmon = 0;
ftrap = 0;
fobj = 0;
level.buriedobjlist = 0;
billobjs = 0;
/* level.bonesinfo = 0; -- handled by savecemetery() */
}
save_engravings(fd, mode);
savedamage(fd, mode);
save_regions(fd, mode);
if (mode != FREE_SAVE) bflush(fd);
}
STATIC_OVL void
savelevl(fd, rlecomp)
int fd;
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 {
/* the run has been broken,
* write out run-length encoding */
writeout:
bwrite(fd, (genericptr_t)&match, sizeof(uchar));
bwrite(fd, (genericptr_t)rgrm, sizeof(struct rm));
/* start encoding again. we have at least 1 rm
* in the next run, viz. this one. */
match = 1;
rgrm = prm;
}
}
}
if (match > 0) {
bwrite(fd, (genericptr_t)&match, sizeof(uchar));
bwrite(fd, (genericptr_t)rgrm, sizeof(struct rm));
}
} else
#endif /* RLECOMP */
bwrite(fd,(genericptr_t) levl,sizeof(levl));
}
/*ARGSUSED*/
void
bufon(fd)
int fd;
{
(*saveprocs.save_bufon)(fd);
return;
}
/*ARGSUSED*/
void
bufoff(fd)
int fd;
{
(*saveprocs.save_bufoff)(fd);
return;
}
void
bflush(fd) /* flush run and buffer */
register int fd;
{
(*saveprocs.save_bflush)(fd);
return;
}
void
bwrite(fd, loc, num)
int fd;
genericptr_t loc;
register unsigned num;
{
(*saveprocs.save_bwrite)(fd, loc, num);
return;
}
void
bclose(fd)
int fd;
{
(*saveprocs.save_bclose)(fd);
return;
}
static int bw_fd = -1;
static FILE *bw_FILE = 0;
static boolean buffering = FALSE;
STATIC_OVL void
def_bufon(fd)
int fd;
{
#ifdef UNIX
if(bw_fd != fd) {
if(bw_fd >= 0)
panic("double buffering unexpected");
bw_fd = fd;
if((bw_FILE = fdopen(fd, "w")) == 0)
panic("buffering of file %d failed", fd);
}
#endif
buffering = TRUE;
}
STATIC_OVL void
def_bufoff(fd)
int fd;
{
def_bflush(fd);
buffering = FALSE;
}
STATIC_OVL void
def_bflush(fd)
int fd;
{
#ifdef UNIX
if(fd == bw_fd) {
if(fflush(bw_FILE) == EOF)
panic("flush of savefile failed!");
}
#endif
return;
}
STATIC_OVL void
def_bwrite(fd,loc,num)
register int fd;
register genericptr_t loc;
register unsigned num;
{
boolean failed;
#ifdef MFLOPPY
bytes_counted += num;
if (count_only) return;
#endif
#ifdef UNIX
if (buffering) {
if(fd != bw_fd)
panic("unbuffered write to fd %d (!= %d)", fd, bw_fd);
failed = (fwrite(loc, (int)num, 1, bw_FILE) != 1);
} else
#endif /* UNIX */
{
/* lint wants the 3rd arg of write to be an int; lint -p an unsigned */
#if defined(BSD) || defined(ULTRIX) || defined(WIN32) || defined(_MSC_VER)
failed = ((long)write(fd, loc, (int)num) != (long)num);
#else /* e.g. SYSV, __TURBOC__ */
failed = ((long)write(fd, loc, num) != (long)num);
#endif
}
if (failed) {
#if defined(UNIX) || defined(VMS) || defined(__EMX__)
if (program_state.done_hup)
terminate(EXIT_FAILURE);
else
#endif
panic("cannot write %u bytes to file #%d", num, fd);
}
}
void
def_bclose(fd)
int fd;
{
bufoff(fd);
#ifdef UNIX
if (fd == bw_fd) {
(void) fclose(bw_FILE);
bw_fd = -1;
bw_FILE = 0;
} else
#endif
(void) close(fd);
return;
}
#ifdef ZEROCOMP
/* The runs of zero-run compression are flushed after the game state or a
* level is written out. This adds a couple bytes to a save file, where
* the runs could be mashed together, but it allows gluing together game
* state and level files to form a save file, and it means the flushing
* does not need to be specifically called for every other time a level
* file is written out.
*/
#define RLESC '\0' /* Leading character for run of LRESC's */
#define flushoutrun(ln) (zerocomp_bputc(RLESC), zerocomp_bputc(ln), ln = -1)
#ifndef ZEROCOMP_BUFSIZ
# define ZEROCOMP_BUFSIZ BUFSZ
#endif
static NEARDATA unsigned char outbuf[ZEROCOMP_BUFSIZ];
static NEARDATA unsigned short outbufp = 0;
static NEARDATA short outrunlength = -1;
static NEARDATA int bwritefd;
static NEARDATA boolean compressing = FALSE;
/*dbg()
{
HUP printf("outbufp %d outrunlength %d\n", outbufp,outrunlength);
}*/
STATIC_OVL void
zerocomp_bputc(c)
int c;
{
#ifdef MFLOPPY
bytes_counted++;
if (count_only)
return;
#endif
if (outbufp >= sizeof outbuf) {
(void) write(bwritefd, outbuf, sizeof outbuf);
outbufp = 0;
}
outbuf[outbufp++] = (unsigned char)c;
}
/*ARGSUSED*/
void
STATIC_OVL zerocomp_bufon(fd)
int fd;
{
compressing = TRUE;
return;
}
/*ARGSUSED*/
STATIC_OVL void
zerocomp_bufoff(fd)
int fd;
{
if (outbufp) {
outbufp = 0;
panic("closing file with buffered data still unwritten");
}
outrunlength = -1;
compressing = FALSE;
return;
}
STATIC_OVL void
zerocomp_bflush(fd) /* flush run and buffer */
register int fd;
{
bwritefd = fd;
if (outrunlength >= 0) { /* flush run */
flushoutrun(outrunlength);
}
#ifdef MFLOPPY
if (count_only) outbufp = 0;
#endif
if (outbufp) {
if (write(fd, outbuf, outbufp) != outbufp) {
#if defined(UNIX) || defined(VMS) || defined(__EMX__)
if (program_state.done_hup)
terminate(EXIT_FAILURE);
else
#endif
zerocomp_bclose(fd); /* panic (outbufp != 0) */
}
outbufp = 0;
}
}
STATIC_OVL void
zerocomp_bwrite(fd, loc, num)
int fd;
genericptr_t loc;
register unsigned num;
{
register unsigned char *bp = (unsigned char *)loc;
if (!compressing) {
#ifdef MFLOPPY
bytes_counted += num;
if (count_only) return;
#endif
if ((unsigned) write(fd, loc, num) != num) {
#if defined(UNIX) || defined(VMS) || defined(__EMX__)
if (program_state.done_hup)
terminate(EXIT_FAILURE);
else
#endif
panic("cannot write %u bytes to file #%d", num, fd);
}
} else {
bwritefd = fd;
for (; num; num--, bp++) {
if (*bp == RLESC) { /* One more char in run */
if (++outrunlength == 0xFF) {
flushoutrun(outrunlength);
}
} else { /* end of run */
if (outrunlength >= 0) { /* flush run */
flushoutrun(outrunlength);
}
zerocomp_bputc(*bp);
}
}
}
}
void
zerocomp_bclose(fd)
int fd;
{
zerocomp_bufoff(fd);
(void) close(fd);
return;
}
#endif /* ZEROCOMP */
STATIC_OVL void
savelevchn(fd, mode)
register int fd, mode;
{
s_level *tmplev, *tmplev2;
int cnt = 0;
for (tmplev = sp_levchn; tmplev; tmplev = tmplev->next) cnt++;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) &cnt, sizeof(int));
for (tmplev = sp_levchn; tmplev; tmplev = tmplev2) {
tmplev2 = tmplev->next;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) tmplev, sizeof(s_level));
if (release_data(mode))
free((genericptr_t) tmplev);
}
if (release_data(mode))
sp_levchn = 0;
}
/* used when saving a level and also when saving dungeon overview data */
void
savecemetery(fd, mode, cemeteryaddr)
int fd;
int mode;
struct cemetery **cemeteryaddr;
{
struct cemetery *thisbones, *nextbones;
int flag;
flag = *cemeteryaddr ? 0 : -1;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t)&flag, sizeof flag);
nextbones = *cemeteryaddr;
while ((thisbones = nextbones) != 0) {
nextbones = thisbones->next;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t)thisbones, sizeof *thisbones);
if (release_data(mode))
free((genericptr_t)thisbones);
}
if (release_data(mode))
*cemeteryaddr = 0;
}
STATIC_OVL void
savedamage(fd, mode)
register int fd, mode;
{
register struct damage *damageptr, *tmp_dam;
unsigned int xl = 0;
damageptr = level.damagelist;
for (tmp_dam = damageptr; tmp_dam; tmp_dam = tmp_dam->next)
xl++;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) &xl, sizeof(xl));
while (xl--) {
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) damageptr, sizeof(*damageptr));
tmp_dam = damageptr;
damageptr = damageptr->next;
if (release_data(mode))
free((genericptr_t)tmp_dam);
}
if (release_data(mode))
level.damagelist = 0;
}
STATIC_OVL void
saveobj(fd, otmp)
int fd;
struct obj *otmp;
{
int buflen, zerobuf = 0;
buflen = sizeof(struct obj);
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
bwrite(fd, (genericptr_t) otmp, buflen);
if (otmp->oextra) {
if (ONAME(otmp)) buflen = strlen(ONAME(otmp)) + 1;
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof buflen);
if (buflen > 0)
bwrite(fd, (genericptr_t) ONAME(otmp), buflen);
/* defer to savemon() for this one */
if (OMONST(otmp)) savemon(fd, OMONST(otmp));
else bwrite(fd, (genericptr_t) &zerobuf, sizeof zerobuf);
if (OMID(otmp)) buflen = sizeof(unsigned);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof buflen);
if (buflen > 0)
bwrite(fd, (genericptr_t) OMID(otmp), buflen);
if (OLONG(otmp)) buflen = sizeof(long);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof buflen);
if (buflen > 0)
bwrite(fd, (genericptr_t) OLONG(otmp), buflen);
if (OMAILCMD(otmp)) buflen = strlen(OMAILCMD(otmp)) + 1;
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof buflen);
if (buflen > 0)
bwrite(fd, (genericptr_t) OMAILCMD(otmp), buflen);
}
}
STATIC_OVL void
saveobjchn(fd, otmp, mode)
register int fd, mode;
register struct obj *otmp;
{
register struct obj *otmp2;
int minusone = -1;
while(otmp) {
otmp2 = otmp->nobj;
if (perform_bwrite(mode)) {
saveobj(fd, otmp);
}
if (Has_contents(otmp))
saveobjchn(fd,otmp->cobj,mode);
if (release_data(mode)) {
/* if (otmp->oclass == FOOD_CLASS) food_disappears(otmp); */
/*
* If these are on the floor, the discarding could
* be because of a 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 == context.victual.piece) {
/* Store the o_id of the victual if mismatched */
if (context.victual.o_id != otmp->o_id)
context.victual.o_id = otmp->o_id;
/* invalidate the pointer; on reload it will get restored */
context.victual.piece = (struct obj *)0;
}
if (otmp == context.tin.tin) {
/* Store the o_id of your tin */
if (context.tin.o_id != otmp->o_id)
context.tin.o_id = otmp->o_id;
/* invalidate the pointer; on reload it will get restored */
context.tin.tin = (struct obj *)0;
}
/* if (otmp->oclass == SPBOOK_CLASS) book_disappears(otmp); */
if (otmp == context.spbook.book) {
/* Store the o_id of your spellbook */
if (context.spbook.o_id != otmp->o_id)
context.spbook.o_id = otmp->o_id;
/* invalidate the pointer; on reload it will get restored */
context.spbook.book = (struct obj *)0;
}
otmp->where = OBJ_FREE; /* set to free so dealloc will work */
otmp->timed = 0; /* not timed any more */
otmp->lamplit = 0; /* caller handled lights */
dealloc_obj(otmp);
}
otmp = otmp2;
}
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) &minusone, sizeof(int));
}
STATIC_OVL void
savemon(fd, mtmp)
int fd;
struct monst *mtmp;
{
int buflen;
buflen = sizeof(struct monst);
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
bwrite(fd, (genericptr_t) mtmp, buflen);
if (mtmp->mextra) {
if (MNAME(mtmp)) buflen = strlen(MNAME(mtmp)) + 1;
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof buflen);
if (buflen > 0)
bwrite(fd, (genericptr_t) MNAME(mtmp), buflen);
if (EGD(mtmp)) buflen = sizeof(struct egd);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
if (buflen > 0)
bwrite(fd, (genericptr_t) EGD(mtmp), buflen);
if (EPRI(mtmp)) buflen = sizeof(struct epri);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
if (buflen > 0)
bwrite(fd, (genericptr_t) EPRI(mtmp), buflen);
if (ESHK(mtmp)) buflen = sizeof(struct eshk);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
if (buflen > 0)
bwrite(fd, (genericptr_t) ESHK(mtmp), buflen);
if (EMIN(mtmp)) buflen = sizeof(struct emin);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
if (buflen > 0)
bwrite(fd, (genericptr_t) EMIN(mtmp), buflen);
if (EDOG(mtmp)) buflen = sizeof(struct edog);
else buflen = 0;
bwrite(fd, (genericptr_t) &buflen, sizeof(int));
if (buflen > 0)
bwrite(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 */
bwrite(fd, (genericptr_t) &MCORPSENM(mtmp),
sizeof MCORPSENM(mtmp));
}
}
STATIC_OVL void
savemonchn(fd, mtmp, mode)
register int fd, mode;
register struct monst *mtmp;
{
register struct monst *mtmp2;
int minusone = -1;
while (mtmp) {
mtmp2 = mtmp->nmon;
if (perform_bwrite(mode)) {
mtmp->mnum = monsndx(mtmp->data);
if (mtmp->ispriest) forget_temple_entry(mtmp); /* EPRI() */
savemon(fd, mtmp);
}
if (mtmp->minvent)
saveobjchn(fd,mtmp->minvent,mode);
if (release_data(mode))
dealloc_monst(mtmp);
mtmp = mtmp2;
}
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) &minusone, sizeof(int));
}
STATIC_OVL void
savetrapchn(fd, trap, mode)
register int fd, mode;
register struct trap *trap;
{
register struct trap *trap2;
while (trap) {
trap2 = trap->ntrap;
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t) trap, sizeof(struct trap));
if (release_data(mode))
dealloc_trap(trap);
trap = trap2;
}
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t)nulls, sizeof(struct trap));
}
/* 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(fd, mode)
register int fd, mode;
{
register struct fruit *f2, *f1;
f1 = ffruit;
while (f1) {
f2 = f1->nextf;
if (f1->fid >= 0 && perform_bwrite(mode))
bwrite(fd, (genericptr_t) f1, sizeof(struct fruit));
if (release_data(mode))
dealloc_fruit(f1);
f1 = f2;
}
if (perform_bwrite(mode))
bwrite(fd, (genericptr_t)nulls, sizeof(struct fruit));
if (release_data(mode))
ffruit = 0;
}
void
store_plname_in_file(fd)
int fd;
{
int plsiztmp = PL_NSIZ;
bufoff(fd);
/* bwrite() before bufon() uses plain write() */
bwrite(fd, (genericptr_t) &plsiztmp, sizeof(plsiztmp));
bwrite(fd, (genericptr_t) plname, plsiztmp);
bufon(fd);
return;
}
STATIC_OVL void
save_msghistory(fd, mode)
int fd, mode;
{
char *msg;
int msgcount = 0, msglen;
int minusone = -1;
boolean init = TRUE;
if (perform_bwrite(mode)) {
/* ask window port for each message in sequence */
while ((msg = getmsghistory(init)) != 0) {
init = FALSE;
msglen = strlen(msg);
/* sanity: truncate if necessary (shouldn't happen);
no need to modify msg[] since terminator isn't written */
if (msglen > BUFSZ - 1) msglen = BUFSZ - 1;
bwrite(fd, (genericptr_t)&msglen, sizeof(msglen));
bwrite(fd, (genericptr_t)msg, msglen);
++msgcount;
}
bwrite(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(fd)
int fd;
{
/* 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.
*
*/
bufoff(fd);
/* bwrite() before bufon() uses plain write() */
bwrite(fd,(genericptr_t)&sfsaveinfo, (unsigned)(sizeof sfsaveinfo));
bufon(fd);
return;
}
void
set_savepref(suitename)
const char *suitename;
{
if (!strcmpi(suitename, "externalcomp")) {
saveprocs.name = "externalcomp";
saveprocs.save_bufon = def_bufon;
saveprocs.save_bufoff = def_bufoff;
saveprocs.save_bflush = def_bflush;
saveprocs.save_bwrite = def_bwrite;
saveprocs.save_bclose = def_bclose;
sfsaveinfo.sfi1 |= SFI1_EXTERNALCOMP;
sfsaveinfo.sfi1 &= ~SFI1_ZEROCOMP;
}
if (!strcmpi(suitename, "!rlecomp")) {
sfsaveinfo.sfi1 &= ~SFI1_RLECOMP;
}
#ifdef ZEROCOMP
if (!strcmpi(suitename, "zerocomp")) {
saveprocs.name = "zerocomp";
saveprocs.save_bufon = zerocomp_bufon;
saveprocs.save_bufoff = zerocomp_bufoff;
saveprocs.save_bflush = zerocomp_bflush;
saveprocs.save_bwrite = zerocomp_bwrite;
saveprocs.save_bclose = zerocomp_bclose;
sfsaveinfo.sfi1 |= SFI1_ZEROCOMP;
sfsaveinfo.sfi1 &= ~SFI1_EXTERNALCOMP;
}
#endif
#ifdef RLECOMP
if (!strcmpi(suitename, "rlecomp")) {
sfsaveinfo.sfi1 |= SFI1_RLECOMP;
}
#endif
}
/* also called by prscore(); this probably belongs in dungeon.c... */
void
free_dungeons()
{
#ifdef FREE_ALL_MEMORY
savelevchn(0, FREE_SAVE);
save_dungeon(0, FALSE, TRUE);
#endif
return;
}
void
freedynamicdata()
{
unload_qtlist();
free_invbuf(); /* let_to_name (invent.c) */
free_youbuf(); /* You_buf,&c (pline.c) */
tmp_at(DISP_FREEMEM, 0); /* temporary display effects */
#ifdef FREE_ALL_MEMORY
# define freeobjchn(X) (saveobjchn(0, X, FREE_SAVE), X = 0)
# define freemonchn(X) (savemonchn(0, X, FREE_SAVE), X = 0)
# define freetrapchn(X) (savetrapchn(0, X, FREE_SAVE), X = 0)
# define freefruitchn() savefruitchn(0, FREE_SAVE)
# define freenames() savenames(0, FREE_SAVE)
# define free_killers() save_killers(0, FREE_SAVE)
# define free_oracles() save_oracles(0, FREE_SAVE)
# define free_waterlevel() save_waterlevel(0, FREE_SAVE)
# define free_worm() save_worm(0, FREE_SAVE)
# define free_timers(R) save_timers(0, FREE_SAVE, R)
# define free_light_sources(R) save_light_sources(0, FREE_SAVE, R);
# define free_engravings() save_engravings(0, FREE_SAVE)
# define freedamage() savedamage(0, FREE_SAVE)
# define free_animals() mon_animal_list(FALSE)
/* move-specific data */
dmonsfree(); /* release dead monsters */
/* level-specific data */
free_timers(RANGE_LEVEL);
free_light_sources(RANGE_LEVEL);
clear_regions();
freemonchn(fmon);
free_worm(); /* release worm segment information */
freetrapchn(ftrap);
freeobjchn(fobj);
freeobjchn(level.buriedobjlist);
freeobjchn(billobjs);
free_engravings();
freedamage();
/* game-state data */
free_killers();
free_timers(RANGE_GLOBAL);
free_light_sources(RANGE_GLOBAL);
freeobjchn(invent);
freeobjchn(migrating_objs);
freemonchn(migrating_mons);
freemonchn(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(iflags.wc_font_map);
if (iflags.wc_font_message) free(iflags.wc_font_message);
if (iflags.wc_font_text) free(iflags.wc_font_text);
if (iflags.wc_font_menu) free(iflags.wc_font_menu);
if (iflags.wc_font_status) free(iflags.wc_font_status);
if (iflags.wc_tile_file) free(iflags.wc_tile_file);
free_autopickup_exceptions();
#endif /* FREE_ALL_MEMORY */
#ifdef STATUS_VIA_WINDOWPORT
status_finish();
#endif
sysopt_release(); /* SYSCF strings */
return;
}
#ifdef MFLOPPY
boolean
swapin_file(lev)
int lev;
{
char to[PATHLEN], from[PATHLEN];
Sprintf(from, "%s%s", permbones, alllevels);
Sprintf(to, "%s%s", levels, alllevels);
set_levelfile_name(from, lev);
set_levelfile_name(to, lev);
if (iflags.checkspace) {
while (level_info[lev].size > freediskspace(to))
if (!swapout_oldest())
return FALSE;
}
if (wizard) {
pline("Swapping in `%s'.", from);
wait_synch();
}
copyfile(from, to);
(void) unlink(from);
level_info[lev].where = ACTIVE;
return TRUE;
}
STATIC_OVL boolean
swapout_oldest() {
char to[PATHLEN], from[PATHLEN];
int i, oldest;
long oldtime;
if (!ramdisk)
return FALSE;
for (i = 1, oldtime = 0, oldest = 0; i <= maxledgerno(); i++)
if (level_info[i].where == ACTIVE
&& (!oldtime || level_info[i].time < oldtime)) {
oldest = i;
oldtime = level_info[i].time;
}
if (!oldest)
return FALSE;
Sprintf(from, "%s%s", levels, alllevels);
Sprintf(to, "%s%s", permbones, alllevels);
set_levelfile_name(from, oldest);
set_levelfile_name(to, oldest);
if (wizard) {
pline("Swapping out `%s'.", from);
wait_synch();
}
copyfile(from, to);
(void) unlink(from);
level_info[oldest].where = SWAPPED;
return TRUE;
}
STATIC_OVL void
copyfile(from, to)
char *from, *to;
{
# ifdef TOS
if (_copyfile(from, to))
panic("Can't copy %s to %s", from, to);
# else
char buf[BUFSIZ]; /* this is system interaction, therefore
* BUFSIZ instead of NetHack's BUFSZ */
int nfrom, nto, fdfrom, fdto;
if ((fdfrom = open(from, O_RDONLY | O_BINARY, FCMASK)) < 0)
panic("Can't copy from %s !?", from);
if ((fdto = open(to, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, FCMASK)) < 0)
panic("Can't copy to %s", to);
do {
nfrom = read(fdfrom, buf, BUFSIZ);
nto = write(fdto, buf, nfrom);
if (nto != nfrom)
panic("Copyfile failed!");
} while (nfrom == BUFSIZ);
(void) close(fdfrom);
(void) close(fdto);
# endif /* TOS */
}
void
co_false() /* see comment in bones.c */
{
count_only = FALSE;
return;
}
#endif /* MFLOPPY */
/*save.c*/