1) consolidate all core usage of `errno' in files.c; 2) give more feedback for any failure by create_levelfile or open_levelfile, similar to what was being done for problems during level change; 3) include trickery info in paniclog (many instances of "trickery" seem to be due to disk or quota problems rather than user misbehavior...). The create_levelfile call in pcmain probably ought to be changed to use error feedback, but in the meantime this should continue working. Perhaps error() should be modified to update paniclog too, but I didn't want to go through all its port-specific incarnations making changes.
1118 lines
27 KiB
C
1118 lines
27 KiB
C
/* SCCS Id: @(#)save.c 3.4 2002/08/22 */
|
|
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "hack.h"
|
|
#include "lev.h"
|
|
#include "quest.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
|
|
|
|
#ifdef ZEROCOMP
|
|
STATIC_DCL void FDECL(bputc, (int));
|
|
#endif
|
|
STATIC_DCL void FDECL(savelevchn, (int,int));
|
|
STATIC_DCL void FDECL(savedamage, (int,int));
|
|
STATIC_DCL void FDECL(saveobjchn, (int,struct obj *,int));
|
|
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));
|
|
#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 */
|
|
#ifdef GCC_WARN
|
|
static long nulls[10];
|
|
#else
|
|
#define nulls nul
|
|
#endif
|
|
|
|
#if defined(UNIX) || defined(VMS) || defined(__EMX__) || defined(WIN32)
|
|
#define HUP if (!program_state.done_hup)
|
|
#else
|
|
#define HUP
|
|
#endif
|
|
|
|
/* 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()) {
|
|
program_state.something_worth_saving = 0;
|
|
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;
|
|
}
|
|
|
|
|
|
#if defined(UNIX) || defined(VMS) || defined (__EMX__) || defined(WIN32)
|
|
/*ARGSUSED*/
|
|
void
|
|
hangup(sig_unused) /* called as signal() handler, so sent at least one arg */
|
|
int sig_unused;
|
|
{
|
|
# ifdef NOSAVEONHANGUP
|
|
(void) signal(SIGINT, SIG_IGN);
|
|
clearlocks();
|
|
# ifndef VMS
|
|
terminate(EXIT_FAILURE);
|
|
# endif
|
|
# else /* SAVEONHANGUP */
|
|
if (!program_state.done_hup++) {
|
|
if (program_state.something_worth_saving)
|
|
(void) dosave0();
|
|
# ifdef VMS
|
|
/* don't call exit when already within an exit handler;
|
|
that would cancel any other pending user-mode handlers */
|
|
if (!program_state.exiting)
|
|
# endif
|
|
{
|
|
clearlocks();
|
|
terminate(EXIT_FAILURE);
|
|
}
|
|
}
|
|
# endif
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* returns 1 if save successful */
|
|
int
|
|
dosave0()
|
|
{
|
|
const char *fq_save;
|
|
register int fd, ofd;
|
|
xchar ltmp;
|
|
d_level uz_save;
|
|
char whynot[BUFSZ];
|
|
|
|
if (!SAVEF[0])
|
|
return 0;
|
|
fq_save = fqname(SAVEF, SAVEPREFIX, 1); /* level files take 0 */
|
|
|
|
#if defined(UNIX) || defined(VMS)
|
|
(void) signal(SIGHUP, 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) {
|
|
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') {
|
|
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);
|
|
#ifdef STORE_PLNAME_IN_FILE
|
|
bwrite(fd, (genericptr_t) plname, PL_NSIZ);
|
|
#endif
|
|
ustuck_id = (u.ustuck ? u.ustuck->m_id : 0);
|
|
#ifdef STEED
|
|
usteed_id = (u.usteed ? u.usteed->m_id : 0);
|
|
#endif
|
|
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;
|
|
#ifdef STEED
|
|
u.usteed = (struct monst *)0;
|
|
#endif
|
|
|
|
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 pline("%s", whynot);
|
|
(void) close(fd);
|
|
(void) delete_savefile();
|
|
HUP killer = whynot;
|
|
HUP done(TRICKED);
|
|
return(0);
|
|
}
|
|
minit(); /* ZEROCOMP */
|
|
getlev(ofd, hackpid, ltmp, FALSE);
|
|
(void) close(ofd);
|
|
bwrite(fd, (genericptr_t) <mp, 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);
|
|
compress(fq_save);
|
|
return(1);
|
|
}
|
|
|
|
STATIC_OVL void
|
|
savegamestate(fd, mode)
|
|
register int fd, mode;
|
|
{
|
|
int uid;
|
|
|
|
#ifdef MFLOPPY
|
|
count_only = (mode & COUNT_SAVE);
|
|
#endif
|
|
uid = getuid();
|
|
bwrite(fd, (genericptr_t) &uid, sizeof uid);
|
|
bwrite(fd, (genericptr_t) &flags, sizeof(struct flag));
|
|
bwrite(fd, (genericptr_t) &u, sizeof(struct you));
|
|
|
|
/* 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);
|
|
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);
|
|
#ifdef STEED
|
|
if(usteed_id)
|
|
bwrite(fd, (genericptr_t) &usteed_id, sizeof usteed_id);
|
|
#endif
|
|
bwrite(fd, (genericptr_t) pl_character, sizeof pl_character);
|
|
bwrite(fd, (genericptr_t) pl_fruit, sizeof pl_fruit);
|
|
bwrite(fd, (genericptr_t) ¤t_fruit, sizeof current_fruit);
|
|
savefruitchn(fd, mode);
|
|
savenames(fd, mode);
|
|
save_waterlevel(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) {
|
|
pline("%s", whynot);
|
|
pline("Probably someone removed it.");
|
|
killer = 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);
|
|
pline("%s", whynot);
|
|
killer = whynot;
|
|
done(TRICKED);
|
|
}
|
|
(void) close(fd);
|
|
|
|
fd = create_levelfile(0, whynot);
|
|
if (fd < 0) {
|
|
pline("%s", whynot);
|
|
killer = 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);
|
|
#ifdef STORE_PLNAME_IN_FILE
|
|
bwrite(fd, (genericptr_t) plname, PL_NSIZ);
|
|
#endif
|
|
ustuck_id = (u.ustuck ? u.ustuck->m_id : 0);
|
|
#ifdef STEED
|
|
usteed_id = (u.usteed ? u.usteed->m_id : 0);
|
|
#endif
|
|
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
|
|
|
|
/* 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
|
|
#ifdef RLECOMP
|
|
{
|
|
/* perform run-length encoding of rm structs */
|
|
struct rm *prm, *rgrm;
|
|
int x, y;
|
|
uchar match;
|
|
|
|
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) {
|
|
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
|
|
bwrite(fd,(genericptr_t) levl,sizeof(levl));
|
|
#endif /* RLECOMP */
|
|
|
|
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:
|
|
/* 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;
|
|
}
|
|
save_engravings(fd, mode);
|
|
savedamage(fd, mode);
|
|
save_regions(fd, mode);
|
|
if (mode != FREE_SAVE) bflush(fd);
|
|
}
|
|
|
|
#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) (bputc(RLESC), 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
|
|
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
|
|
bufon(fd)
|
|
int fd;
|
|
{
|
|
compressing = TRUE;
|
|
return;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
bufoff(fd)
|
|
int fd;
|
|
{
|
|
if (outbufp) {
|
|
outbufp = 0;
|
|
panic("closing file with buffered data still unwritten");
|
|
}
|
|
outrunlength = -1;
|
|
compressing = FALSE;
|
|
return;
|
|
}
|
|
|
|
void
|
|
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
|
|
bclose(fd); /* panic (outbufp != 0) */
|
|
}
|
|
outbufp = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
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);
|
|
}
|
|
bputc(*bp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
bclose(fd)
|
|
int fd;
|
|
{
|
|
bufoff(fd);
|
|
(void) close(fd);
|
|
return;
|
|
}
|
|
|
|
#else /* ZEROCOMP */
|
|
|
|
static int bw_fd = -1;
|
|
static FILE *bw_FILE = 0;
|
|
static boolean buffering = FALSE;
|
|
|
|
void
|
|
bufon(fd)
|
|
int fd;
|
|
{
|
|
#ifdef UNIX
|
|
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;
|
|
}
|
|
|
|
void
|
|
bufoff(fd)
|
|
int fd;
|
|
{
|
|
bflush(fd);
|
|
buffering = FALSE;
|
|
}
|
|
|
|
void
|
|
bflush(fd)
|
|
int fd;
|
|
{
|
|
#ifdef UNIX
|
|
if(fd == bw_fd) {
|
|
if(fflush(bw_FILE) == EOF)
|
|
panic("flush of savefile failed!");
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
void
|
|
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)
|
|
failed = (write(fd, loc, (int)num) != (int)num);
|
|
#else /* e.g. SYSV, __TURBOC__ */
|
|
failed = (write(fd, loc, num) != 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
|
|
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;
|
|
}
|
|
#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;
|
|
}
|
|
|
|
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
|
|
saveobjchn(fd, otmp, mode)
|
|
register int fd, mode;
|
|
register struct obj *otmp;
|
|
{
|
|
register struct obj *otmp2;
|
|
unsigned int xl;
|
|
int minusone = -1;
|
|
|
|
while(otmp) {
|
|
otmp2 = otmp->nobj;
|
|
if (perform_bwrite(mode)) {
|
|
xl = otmp->oxlth + otmp->onamelth;
|
|
bwrite(fd, (genericptr_t) &xl, sizeof(int));
|
|
bwrite(fd, (genericptr_t) otmp, xl + sizeof(struct obj));
|
|
}
|
|
if (Has_contents(otmp))
|
|
saveobjchn(fd,otmp->cobj,mode);
|
|
if (release_data(mode)) {
|
|
if (otmp->oclass == FOOD_CLASS) food_disappears(otmp);
|
|
if (otmp->oclass == SPBOOK_CLASS) book_disappears(otmp);
|
|
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
|
|
savemonchn(fd, mtmp, mode)
|
|
register int fd, mode;
|
|
register struct monst *mtmp;
|
|
{
|
|
register struct monst *mtmp2;
|
|
unsigned int xl;
|
|
int minusone = -1;
|
|
struct permonst *monbegin = &mons[0];
|
|
|
|
if (perform_bwrite(mode))
|
|
bwrite(fd, (genericptr_t) &monbegin, sizeof(monbegin));
|
|
|
|
while (mtmp) {
|
|
mtmp2 = mtmp->nmon;
|
|
if (perform_bwrite(mode)) {
|
|
xl = mtmp->mxlth + mtmp->mnamelth;
|
|
bwrite(fd, (genericptr_t) &xl, sizeof(int));
|
|
bwrite(fd, (genericptr_t) mtmp, xl + sizeof(struct monst));
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* 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_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);
|
|
freemonchn(fmon);
|
|
free_worm(); /* release worm segment information */
|
|
freetrapchn(ftrap);
|
|
freeobjchn(fobj);
|
|
freeobjchn(level.buriedobjlist);
|
|
freeobjchn(billobjs);
|
|
free_engravings();
|
|
freedamage();
|
|
|
|
/* game-state data */
|
|
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);
|
|
|
|
#endif /* FREE_ALL_MEMORY */
|
|
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;
|
|
}
|
|
# ifdef WIZARD
|
|
if (wizard) {
|
|
pline("Swapping in `%s'", from);
|
|
wait_synch();
|
|
}
|
|
# endif
|
|
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);
|
|
# ifdef WIZARD
|
|
if (wizard) {
|
|
pline("Swapping out `%s'.", from);
|
|
wait_synch();
|
|
}
|
|
# endif
|
|
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*/
|