[net.games.hack] save file format is not well designed

chris@umcp-cs.UUCP (Chris Torek) (12/21/85)

Index: src/hack.save.c src/hack.lev.c Hack_1.0.3 Fix_(proposed)

Description:
	The save file format makes the assumption that the `permonst'
	pointers in monster chains are always at a constant offset
	from &mons[0].  Unfortunately, after changes to the source,
	this is not always true.  The bug manifests as dogs or
	other `special' monsters being displayed with the wrong
	symbol, and a segmentation fault soon afterward, as invalid
	pointers are soon followed.

Repeat-By:
	Somewhat difficult.  I invoked the problem entirely by
	accident when adding a global variable.

Fix:
	(proposed)

	The save file format should be changed.  Instead of each
	monster's permonst pointer, an index value should be saved.
	For `normal' monsters this can simply be the index in
	`mons'.  For `special' monsters this should be a special
	value for each.

	An easy way to get the required information into the save
	file would be to add the index to the permonst structure
	itself, so that the save and restore code changes to
	something like this:

		mtmp->data = (struct permonst *)(msave->data->index);
		bwrite(fd, (char *)mtmp, sizeof *mtmp);
		free(mtmp);

	and

		mread(fd, mtmp, sizeof *mtmp);
		index = (int)mtmp->data;
		if (index < CMNUM)
			mtmp->data = mons[index];
		else
			switch (index) {
			case <cases for each `special' monster>
			.
			.
			.
			}

	Alternatively, the current scheme could continue to be used
	if the `special' monster permonst data were moved into the
	`mons' array, so that the (rather hacky) relocation trick
	works for these as well.  This would appear to require
	changes in many modules.

	At the same time as any change is made, the save file format
	should be modified to include a `version' or `magic number',
	so that the format can later be changed and the recover code
	augmented to recognize an `old format' file.  Unfortunately,
	no provision exists for this at the moment.  However, a
	reasonably safe hack is to note that current save files
	start with the saved level, which starts with `hackpid';
	this is always a nonnegative integer.  If the `magic numbers'
	or versions are made negative, hack can still recognize old
	format files.

	For those that have held on this long, here is a revised
	version of restmonchn() that is a little shorter than the
	original.  (I felt I should post at least one *real* change
	:-).)

struct monst *
restmonchn(fd)
	register int fd;
{
	register struct monst *mtmp, **p;
	register long differ;
	struct monst *first = 0;
	struct permonst *monbegin;
	int xl;

	mread(fd, (char *)&monbegin, sizeof monbegin);
	differ = (char *)&mons[0] - (char *)monbegin;

	p = &first;
	while (1) {
		mread(fd, (char *)&xl, sizeof xl);
		if (xl == -1)
			break;
		mtmp = newmonst(xl);
		mread(fd, (char *)mtmp, (unsigned)xl + sizeof (struct monst));
		if (!mtmp->m_id)
			mtmp->m_id = flags.ident++;
		mtmp->data = (struct permonst *)((char *)mtmp->data + differ);
		if (mtmp->minvent)
			mtmp->minvent = restobjchn(fd);
		*p = mtmp;
		p = &mtmp->nmon;
	}
	if (*p) {
		impossible("Restmonchn: error reading monchn.");
		*p = 0;
	}
	return (first);
}
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 4251)
UUCP:	seismo!umcp-cs!chris
CSNet:	chris@umcp-cs		ARPA:	chris@mimsy.umd.edu