/* db_rw.c -- I/O to/from flat file databases */

#include "copyright.h"

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */

#include <sys/file.h>
#include <sys/types.h>

#include "mudconf.h"
#include "config.h"
#include "externs.h"
#include "db.h"

extern const char *getstring_noalloc(FILE * f);
extern void putstring(FILE * f, const char *s);
extern void db_grow(dbref newtop);
extern struct object *db;

static int g_version;
static int g_format;
static int g_flags;

/* ---------------------------------------------------------------------------
 * getboolexp1: Get boolean subexpression from file.
 */

static struct boolexp *getboolexp1(FILE *f)
{
struct boolexp *b;
char	*buff, *s;
int	c, d, anum;

	c = getc(f);
	switch (c) {
	case '\n':
		ungetc(c, f);
		return TRUE_BOOLEXP;
		/* break; */
	case EOF:
		abort();	/* unexpected EOF in boolexp */
		break;
	case '(':
		b = alloc_bool("getboolexp1.openparen");
		switch (c = getc(f)) {
		case NOT_TOKEN:
			b->type = BOOLEXP_NOT;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		case INDIR_TOKEN:
			b->type = BOOLEXP_INDIR;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		case IS_TOKEN:
			b->type = BOOLEXP_IS;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		case CARRY_TOKEN:
			b->type = BOOLEXP_CARRY;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		case OWNER_TOKEN:
			b->type = BOOLEXP_OWNER;
			b->sub1 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		default:
			ungetc(c, f);
			b->sub1 = getboolexp1(f);
			if ((c = getc(f)) == '\n') c = getc(f);
			switch (c) {
			case AND_TOKEN:
				b->type = BOOLEXP_AND;
				break;
			case OR_TOKEN:
				b->type = BOOLEXP_OR;
				break;
			default:
				goto error;
			}
			b->sub2 = getboolexp1(f);
			if ((d = getc(f)) == '\n') d = getc(f);
			if (d != ')') goto error;
			return b;
		}
	case '-':	/* obsolete NOTHING key, eat it */
		while ((c = getc(f)) != '\n')
		if (c == EOF)
			abort();		/* unexp EOF */
		ungetc(c, f);
		return TRUE_BOOLEXP;
	default:				/* dbref or attribute */
		ungetc(c, f);
		b = alloc_bool("getboolexp1.default");
		b->type = BOOLEXP_CONST;
		b->thing = 0;

		/* This is either an attribute or a constant lock.  Constant
		 * locks are of the form <num>, while attribute locks are of
		 * the form <aname>:<string> or <anum>:<string>.
		 * The characters <nl>, |, and & terminate the string.
		 */

		if (isdigit(c)) {
			while (isdigit(c = getc(f))) {
				b->thing = b->thing * 10 + c - '0';
			}
		} else if (isalpha(c)) {
			buff = alloc_lbuf("getboolexp1.atr_name");
			for (s=buff;					
			     ((c=getc(f))!=EOF) && (c!='\n') && (c!=':');
			     *s++=c);
			if (c == EOF) {
				free_lbuf(buff);
				free_bool(b);
				goto error;
			}
			*s = '\0';

			/* Look the name up as an attribute.  If not found,
			 * create a new attribute.
			 */

			anum = mkattr(buff);
			if (anum <= 0) {
				free_bool(b);
				free_lbuf(buff);
				goto error;
			}
			free_lbuf(buff);
			b->thing = anum;
		} else {
			free_bool(b);
			goto error;
		}

		/* if last character is : then this is an attribute lock */

		if (c == ':') {
			buff = alloc_lbuf("getboolexp1.attr_lock");
			for (s=buff;
			     ((c=getc(f))!=EOF) && (c!='\n') && (c!=')') &&
			      (c!=OR_TOKEN) && (c!=AND_TOKEN);
			     *s++=c);
			if (c == EOF)
				goto error;
			*s++ = 0;
			b->sub1 = (struct boolexp *)strsave(buff);
			b->type = BOOLEXP_ATR;
			free_lbuf(buff);
		}
		ungetc(c, f);
		return b;
	}

error:
	abort();			/* bomb out */
	return TRUE_BOOLEXP;
}

/* ---------------------------------------------------------------------------
 * getboolexp: Read a boolean expression from the flat file.
 */

static struct boolexp *getboolexp(FILE *f)
{
struct boolexp *b;
char	c;

	b = getboolexp1(f);
	if (getc(f) != '\n')
		abort();	/* parse error, we lose */

	/* MUSH (except for PernMUSH) and MUSE can have an extra CR,
	 * MUD does not. */

	if (((g_format == F_MUSH) && (g_version != 2)) ||
	    (g_format == F_MUSE)) {
		if ((c = getc(f)) != '\n') ungetc(c, f);
	}
	return b;
}

/* ---------------------------------------------------------------------------
 * unscramble_attrnum: Fix up attribute numbers from foreign muds
 */

static int unscramble_attrnum (int attrnum)
{
char	anam[4];

	switch (g_format) {
	case F_MUSE:
		switch (attrnum) {
		case 39:	return A_IDLE;
		case 40:	return A_AWAY;
		case 41:	return 0;	/* mailk */
		case 42:	return A_ALIAS;
		case 43:	return A_EFAIL;
		case 44:	return A_OEFAIL;
		case 45:	return A_AEFAIL;
		case 46:	return 0;	/* it */
		case 47:	return A_LEAVE;
		case 48:	return A_OLEAVE;
		case 49:	return A_ALEAVE;
		case 50:	return 0;	/* channel */
		case 51:	return A_QUOTA;
		case 52:	return A_TEMP;	/* temp for pennies */
		case 53:	return 0;	/* huhto */
		case 54:	return 0;	/* haven */
		case 57:	return mkattr((char *)"TZ");
		case 58:	return 0;	/* doomsday */
		case 59:	return mkattr((char *)"Email");
		case 98:	return mkattr((char *)"Status");
		case 99:	return mkattr((char *)"Race");
		default:	return attrnum;
		}
	case F_MUSH:

		/* Only need to muck with Pern variants */

		if (g_version != 2)
			return attrnum;
		switch (attrnum) {
		case 34:	return A_OENTER;
		case 41:	return A_LEAVE;
		case 42:	return A_ALEAVE;
		case 43:	return A_OLEAVE;
		case 44:	return A_OXENTER;
		case 45:	return A_OXLEAVE;
		default:
			if ((attrnum >= 126) && (attrnum < 152)) {
				anam[0]='W';
				anam[1]=attrnum - 126 + 'A';
				anam[2]='\0';
				return mkattr(anam);
			}
			if ((attrnum >= 152) && (attrnum < 178)) {
				anam[0]='X';
				anam[1]=attrnum - 152 + 'A';
				anam[2]='\0';
				return mkattr(anam);
			}
			return attrnum;
		}
	default:
		return attrnum;
	}
}

/* ---------------------------------------------------------------------------
 * get_list: Read attribute list from flat file.
 */

static int get_list(FILE * f, dbref i)
{
dbref	atr, aowner;
int	c, aflags, xflags, anum;
char	*buff, *buf2, *buf2p, *ownp, *flagp;

	buff = alloc_lbuf("get_list");
	while (1) {
		switch (c = getc(f)) {
		case '>':	/* read # then string */
			atr = unscramble_attrnum(getref(f));
			if (atr > 0) {
				/* Store the attr */

				atr_add_raw(i, atr,
					(char *)getstring_noalloc(f));
			} else {
				/* Silently discard */

				getstring_noalloc(f);
			}
			break;
		case ']':	/* Pern 1.13 style text attribute */
			strcpy(buff, (char *)getstring_noalloc(f));

			/* Get owner number */

			ownp = (char *)index(buff, '^');
			if (!ownp) {
				fprintf(stderr,
					"Bad format in attribute on object %d\n",
					i);
				free_lbuf(buff);
				return 0;
			}
			*ownp++ = '\0';

			/* Get attribute flags */

			flagp = (char *)index(ownp, '^');
			if (!flagp) {
				fprintf(stderr,
					"Bad format in attribute on object %d\n",
					i);
				free_lbuf(buff);
				return 0;
			}
			*flagp++ = '\0';

			/* Convert Pern-style owner and flags to 2.0 format */

			aowner = atoi(ownp);
			xflags = atoi(flagp);
			aflags = 0;

			if (!aowner) aowner = NOTHING;
			if (xflags & 0x10) aflags |= AF_LOCK|AF_NOPROG;
			if (xflags & 0x20) aflags |= AF_NOPROG;

			/* Look up the attribute name in the attribute table.
			 * If the name isn't found, create a new attribute.
			 * If the create fails, try prefixing the attr name
			 * with ATR_ (Pern allows attributes to start with a
			 * non-alphabetic character.
			 */

			anum = mkattr(buff);
			if (anum < 0) {
				buf2 = alloc_mbuf("get_list.new_attr_name");
				buf2p = buf2;
				safe_mb_str((char *)"ATR_", buf2, &buf2p);
				safe_mb_str(buff, buf2, &buf2p);
				*buf2p = '\0';
				anum = mkattr(buf2);
				free_mbuf(buf2);
			}
			if (anum < 0) {
				fprintf(stderr,
					"Bad attribute name '%s' on object %d, ignoring...\n",
					buff, i);
				(void)getstring_noalloc(f);
			} else {
				atr_add(i, anum, (char *)getstring_noalloc(f),
					aowner, aflags);
			}
			break;
		case '\n':	/* ignore newlines. They're due to v(r). */
			break;
		case '<':	/* end of list */
			free_lbuf(buff);
			if ('\n' != getc(f)) {
				fprintf(stderr,
					"No line feed on object %d\n", i);
				return 0;
			}
			return 1;
		default:
			fprintf(stderr,
				"Bad character '%c' when getting attributes on object %d\n",
				c, i);
			free_lbuf(buff);
			return 0;
		}
	}
}

/* ---------------------------------------------------------------------------
 * putbool_subexp: Write a boolean sub-expression to the flat file.
 */
static void putbool_subexp(FILE * f, struct boolexp * b)
{
	switch (b->type) {
	case BOOLEXP_IS:
		putc('(', f);
		putc(IS_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_CARRY:
		putc('(', f);
		putc(CARRY_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_INDIR:
		putc('(', f);
		putc(INDIR_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_OWNER:
		putc('(', f);
		putc(OWNER_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_AND:
		putc('(', f);
		putbool_subexp(f, b->sub1);
		putc(AND_TOKEN, f);
		putbool_subexp(f, b->sub2);
		putc(')', f);
		break;
	case BOOLEXP_OR:
		putc('(', f);
		putbool_subexp(f, b->sub1);
		putc(OR_TOKEN, f);
		putbool_subexp(f, b->sub2);
		putc(')', f);
		break;
	case BOOLEXP_NOT:
		putc('(', f);
		putc(NOT_TOKEN, f);
		putbool_subexp(f, b->sub1);
		putc(')', f);
		break;
	case BOOLEXP_CONST:
		fprintf(f, "%d", b->thing);
		break;
	case BOOLEXP_ATR:
		fprintf(f, "%d:%s\n", b->thing, (char *)b->sub1);
		break;
	default:
		fprintf(stderr, "Unknown boolean type in putbool_subexp: %d\n",
			b->type);
	}
}

/* ---------------------------------------------------------------------------
 * putboolexp: Write boolean expression to the flat file.
 */

static void putboolexp(FILE * f, struct boolexp * b)
{
	if (b != TRUE_BOOLEXP) {
		putbool_subexp(f, b);
	}
	putc('\n', f);
}

/* ---------------------------------------------------------------------------
 * upgrade_flags: Convert foreign flags to MUSH format.
 */

static FLAG upgrade_flags (FLAG oldflags, dbref thing, int db_format,
	int db_version)
{
FLAG	newflags;

	if (db_format == F_MUSE) {
		if (db_version == 1)
			return oldflags;

		/* Convert level-based players to normal */

		switch (oldflags & 0xf) {
		case 0:		/* room */
		case 1:		/* thing */
		case 2:		/* exit */
			newflags = oldflags & 0x3;
			break;
		case 8:		/* guest */
		case 9:		/* trial player */
		case 10:	/* member */
		case 11:	/* junior official */
		case 12:	/* official */
			newflags = TYPE_PLAYER;
			break;
		case 13:	/* honorary wizard */
		case 14:	/* administrator */
		case 15:	/* director */
			newflags = TYPE_PLAYER|WIZARD;
			break;
		default:	/* A bad type, mark going */
			fprintf(stderr, "Funny object type for #%d\n", thing);
			return GOING;	
		}

		/* Player #1 is always a wizard */

		if (thing == (dbref)1) newflags |= WIZARD;

		/* Set type-specific flags */

		switch(newflags & TYPE_MASK) {
		case TYPE_PLAYER:	/* Lose CONNECT TERSE QUITE NOWALLS WARPTEXT */
			if (oldflags & 0x10) newflags |= PLAYER_BUILD;
			if (oldflags & 0x80) newflags |= PLAYER_SLAVE;
			if (oldflags & 0x1000) newflags |= PLAYER_UNFIND;
			break;
		case TYPE_THING:	/* lose LIGHT SACR_OK */
			if (oldflags & 0x10) newflags |= THING_KEY;
			if (oldflags & 0x200) newflags |= THING_DEST_OK;
			break;
		case TYPE_ROOM:
			if (oldflags & 0x80) newflags |= ROOM_TEMPLE;
			if (oldflags & 0x200) newflags |= ROOM_ABODE;
			break;
		case TYPE_EXIT:
			if (oldflags & 0x200) newflags |= EXIT_SEETHRU;
		default:
			break;
		}
	
		/* Convert common flags */
		/* Lose: MORTAL ACCESSED MARKED SEE_OK UNIVERSAL */
	
		if (oldflags & 0x20) newflags |= CHOWN_OK;
		if (oldflags & 0x40) newflags |= DARK;
		if (oldflags & 0x100) newflags |= STICKY;
		if (oldflags & 0x400) newflags |= HAVEN;
		if (oldflags & 0x2000) newflags |= INHERIT;
		if (oldflags & 0x4000) newflags |= GOING;
		if (oldflags & 0x20000) newflags |= PUPPET;
		if (oldflags & 0x40000) newflags |= LINK_OK;
		if (oldflags & 0x80000) newflags |= ENTER_OK;
		if (oldflags & 0x100000) newflags |= VISUAL;
		if (oldflags & 0x800000) newflags |= OPAQUE;
		if (oldflags & 0x1000000) newflags |= QUIET;
		return newflags;
	} else if ((db_format == F_MUSH) && (db_version == 2)) {

		/* Pern variants */

		newflags = oldflags &
			(TYPE_MASK|LINK_OK|HAVEN|QUIET|HALT|DARK|
			 GOING|PUPPET|CHOWN_OK|ENTER_OK|VISUAL|OPAQUE);
		if (oldflags & 0x1000000) newflags |= INHERIT;
		if (oldflags & 0x10000000) newflags |= HEARTHRU;
		switch(newflags & TYPE_MASK) {
		case TYPE_PLAYER:
			newflags |= oldflags &
				(WIZARD|PLAYER_BUILD|PLAYER_GAGGED|PLAYER_UNFIND);
			if (oldflags & 0x200000) newflags |= NOSPOOF;
			if (oldflags & 0x4000000) newflags |= PLAYER_SUSPECT;
			break;
		case TYPE_EXIT:
			newflags |= oldflags & (STICKY|EXIT_SEETHRU);
			if (oldflags & 0x8) newflags |= EXIT_KEY;
			if (oldflags & WIZARD) newflags |= INHERIT;
			break;
		case TYPE_THING:
			newflags |= oldflags &
				(THING_KEY|THING_DEST_OK|STICKY);
			if (oldflags & WIZARD) newflags |= INHERIT;
			if (oldflags & 0x80) newflags |= VERBOSE;
			if (oldflags & 0x2000) newflags |= IMMORTAL;
			if (oldflags & 0x200000) newflags |= MONITOR;
			if (oldflags & 0x4000000) newflags |= THING_SAFE;
			break;
		case TYPE_ROOM:
			newflags |= oldflags &
				(ROOM_FLOATING|ROOM_TEMPLE|ROOM_ABODE|
				 ROOM_JUMP_OK);
			if (oldflags & WIZARD) newflags |= INHERIT;
		}
		return newflags;
	} else if ((db_format == F_MUSH) && (db_version == 3)) {

		/* MUSH 2.0 format 3: Clear the ACCESSED bit */

		return (oldflags & ~0x8000);
	} else if ((db_format == F_MUSH) && (db_version == 4)) {

		/* MUSH 2.0 format 4: Clear the MARKED bit */

		return (oldflags & ~0x10000);
	}
	return oldflags;
}

/* ---------------------------------------------------------------------------
 * efo_convert: Fix things up for Exits-From-Objects
 */

void efo_convert ()
{
int	i;
dbref	link;

	DO_WHOLE_DB(i) {
		switch (Typeof(i)) {
		case TYPE_PLAYER:
		case TYPE_THING:

			/* swap Exits and Link */

			link = Link(i);
			s_Link(i, Exits(i));
			s_Exits(i, link);
			break;
		}
	}
}
/* ---------------------------------------------------------------------------
 * unscraw_foreign: Fix up strange object linking conventions for other formats
 */

static void unscraw_pern_object (dbref i, int db_flags)
{
dbref	aowner;
int	aflags;
char	*p_str;

	if (db_flags & V_PERNKEY) {

		/* Use lock on players is really the page lock */

		if (Typeof(i) == TYPE_PLAYER) {
			p_str = atr_get(i, A_LUSE, &aowner, &aflags);
			if (*p_str) {
				atr_add_raw(i, A_LPAGE, p_str);
				atr_clr(i, A_LUSE);
			}
			free_lbuf(p_str);
		}

		/* Enter lock on rooms is the teleport-out lock (not imp) */

		if (Typeof(i) == TYPE_ROOM) {
			atr_clr(i, A_LENTER);
		}
	}
}

void unscraw_foreign(int db_format, int db_version, int db_flags)
{
dbref	tmp, i, aowner;
int	aflags;
char	*p_str;

	switch (db_format) {
	case F_MUSE:
		DO_WHOLE_DB(i) {
			if (Typeof(i) == TYPE_EXIT) {

				/* MUSE exits are bass-ackwards */

				tmp = Exits(i);
				s_Exits(i, Location(i));
				s_Location(i, tmp);
			}
			if (db_version > 3) {

				/* MUSEs with pennies in an attribute have
				 * it stored in attr 255 (see
				 * unscramble_attrnum)
				 */

				p_str = atr_get(i, A_TEMP, &aowner, &aflags);
				s_Pennies(i, atoi(p_str));
				free_lbuf(p_str);
				atr_clr(i, A_TEMP);
			}
		}
		if (!(db_flags & V_LINK)) {
			efo_convert();
		}
		break;
	case F_MUSH:
		if (db_version <= 3) {

			if (db_version == 2) {
				DO_WHOLE_DB(i) {
					unscraw_pern_object(i, db_flags);
				}
			}
			efo_convert();
		}
		break;
	case F_MUD:
		efo_convert();
	}
}

/* ---------------------------------------------------------------------------
 * getlist_discard, get_atrdefs_discard: Throw away data from MUSE that we
 * don't use.
 */

static void getlist_discard (FILE *f)
{
int	count;

	for (count=getref(f); count>0; count--)
		(void)getref(f);
}

static void get_atrdefs_discard (FILE *f)
{
const	char	*sp;

	for (;;) {
		sp = getstring_noalloc(f);	/* flags or endmarker */
		if (*sp == '\\')
			return;
		sp = getstring_noalloc(f);	/* object */
		sp = getstring_noalloc(f);	/* name */
	}
}

dbref db_read(FILE *f, int *db_format, int *db_version, int *db_flags)
{
dbref	i, anum;
char	ch, peek;
const char *tstr;
int	header_gotten, size_gotten, nextattr_gotten;
int	read_attribs, read_name, read_zone, read_link, read_key, read_parent;
int	read_pennies, read_timestamps;
int	read_pern_key, read_pern_comm, read_powers_player, read_powers_any;
int	read_muse_parents, read_muse_atrdefs;
int	deduce_version, deduce_name, deduce_zone, deduce_timestamps;
int	aflags;
struct boolexp *tempbool;

	header_gotten = 0;
	size_gotten = 0;
	nextattr_gotten = 0;
	g_format = F_UNKNOWN;
	g_version = 0;
	g_flags = 0;
	read_attribs = 1;
	read_name = 1;
	read_zone = 0;
	read_link = 0;
	read_key = 1;
	read_parent = 0;
	read_pennies = 1;
	read_timestamps = 0;
	read_pern_key = 0;
	read_pern_comm = 0;
	read_powers_player = 0;
	read_powers_any = 0;
	read_muse_parents = 0;
	read_muse_atrdefs = 0;
	deduce_version = 1;
	deduce_zone = 1;
	deduce_name = 1;
	deduce_timestamps = 1;

#ifdef STANDALONE
	fprintf(stderr, "Reading ");
	fflush(stderr);
#endif
	db_free();
	for (i = 0;; i++) {

#ifdef STANDALONE
		if (!(i % 100)) {
			fputc('.', stderr);
			fflush(stderr);
			tmp_sync();
			cache_reset();
		}
#endif
		switch (ch = getc(f)) {
		case '~':	/* Database size tag */
			if (size_gotten) {
				fprintf(stderr,
					"\nDuplicate size entry at object %d, ignored.\n",
					i);
				tstr = getstring_noalloc(f);    /* junk */
				break;
			}
			mudstate.min_size = getref(f);
			size_gotten = 1;
			break;
		case '+':	/* MUSH 2.0 header */
			switch (ch = getc(f)) {	/* 2nd char selects type */
			case 'V':	/* VERSION */
				if (header_gotten) {
					fprintf(stderr,
						"\nDuplicate MUSH version header entry at object %d, ignored.\n",
						i);
					tstr = getstring_noalloc(f);	/* junk */
					break;
				}
				header_gotten = 1;
				deduce_version = 0;
				g_format = F_MUSH;
				g_version = getref(f);

				if ((g_version & V_MASK) == 2) {

					/* Handle Pern veriants specially */

					switch (g_version >> 8) {
					case 2:
						read_pern_comm = 1;
						g_flags |= V_COMM;
					case 1:
						read_pern_key = 1;
						read_parent = 1;
						g_flags |= V_PERNKEY|V_ZONE;
					}
				} else {

					/* Otherwise extract feature flags */

					if (g_version & V_GDBM) {
						read_attribs = 0;
						read_name = !(g_version & V_ATRNAME);
					}
					read_zone = (g_version & V_ZONE);
					read_link = (g_version & V_LINK);
					read_key = !(g_version & V_ATRKEY);
					read_parent = (g_version & V_PARENT);
					g_flags = g_version & ~V_MASK;
				}
				g_version &= V_MASK;
				deduce_name = 0;
				deduce_version = 0;
				deduce_zone = 0;
				break;
			case 'S':	/* SIZE */
				if (size_gotten) {
					fprintf(stderr,
						"\nDuplicate size entry at object %d, ignored.\n",
						i);
					tstr = getstring_noalloc(f);	/* junk */
				} else {
					mudstate.min_size = getref(f);
				}
				size_gotten = 1;
				break;
			case 'A':	/* USER-NAMED ATTRIBUTE */
				anum = getref(f);
				tstr = getstring_noalloc(f);
				if (isdigit(*tstr)) {
					aflags = 0;
					while (isdigit(*tstr))
						aflags = (aflags * 10) +
							(*tstr++ - '0');
					tstr++;	/* skip ':' */
				} else {
					aflags = mudconf.vattr_flags;
				}
				define_vattr((char *)tstr, anum, aflags);
				break;
			case 'F':	/* OPEN USER ATTRIBUTE SLOT */
				anum = getref(f);
				free_vattr(anum);
				break;
			case 'N':	/* NEXT ATTR TO ALLOC WHEN NO FREELIST */
				if (nextattr_gotten) {
					fprintf(stderr,
						"\nDuplicate next free vattr entry at object %d, ignored.\n",
						i);
					tstr = getstring_noalloc(f);	/* junk */
				} else {
					mudstate.attr_next = getref(f);
					nextattr_gotten = 1;
				}
				break;
			default:
				fprintf(stderr,
					"\nUnexpected character '%c' in MUSH header near object #%d, ignored.\n",
					ch, i);
				tstr = getstring_noalloc(f);	/* toss line */
			}
			break;
		case '@':	/* MUSE header */
			if (header_gotten) {
				fprintf(stderr,
					"\nDuplicate MUSE header entry at object #%d.\n",
					i);
				return -1;
			}
			header_gotten = 1;
			deduce_version = 0;
			g_format = F_MUSE;
			g_version = getref(f);
			deduce_name = 0;
			deduce_zone = 1;
			read_pennies = (g_version <= 3);
			read_link = (g_version >= 5);
			read_powers_player = (g_version >= 6);
			read_powers_any = (g_version == 6);
			read_muse_parents = (g_version >= 8);
			read_muse_atrdefs = (g_version >= 8);
			if (read_link) g_flags |= V_LINK;
			break;
		case '#':
			if (deduce_version) {
				g_format = F_MUD;
				g_version = 1;
				deduce_version = 0;
			}
			if (g_format != F_MUD) {
				fprintf(stderr,
					"\nMUD-style object found in non-MUD database at object #%d\n",
					i);
				return -1;
			}
			if (i != getref(f)) {
				fprintf(stderr,
					"\nSequence error at object #%d\n", i);
					return -1;
			}
			db_grow(i+1);
			s_Name(i, getstring_noalloc(f));
			atr_add_raw(i, A_DESC, (char *)getstring_noalloc(f));
			s_Location(i, getref(f));
			s_Contents(i, getref(f));
			s_Exits(i, getref(f));
			s_Link(i, NOTHING);
			s_Next(i, getref(f));
			s_Zone(i, NOTHING);
			tempbool = getboolexp(f);
			atr_add_raw(i, A_LOCK,
				unparse_boolexp_quiet(1, tempbool));
			free_boolexp(tempbool);
			atr_add_raw(i, A_FAIL, (char *)getstring_noalloc(f));
			atr_add_raw(i, A_SUCC, (char *)getstring_noalloc(f));
			atr_add_raw(i, A_OFAIL, (char *)getstring_noalloc(f));
			atr_add_raw(i, A_OSUCC, (char *)getstring_noalloc(f));
			s_Owner(i, getref(f));
			s_Parent(i, NOTHING);
			s_Pennies(i, getref(f));
			s_Flags(i, upgrade_flags(getref(f), i,
				g_format, g_version));
			s_Pass(i, getstring_noalloc(f));
			if (deduce_timestamps) {
				peek = getc(f);
				if ((peek != '#') && (peek != '*')) {
					read_timestamps = 1;
				}
				deduce_timestamps = 0;
				ungetc(peek,f);
			}
			if (read_timestamps) {
				aflags = getref(f);	/* created */
				aflags = getref(f);	/* lastused */
				aflags = getref(f);	/* usecount */
			}
			break;
		case '&':	/* MUSH 2.0a stub entry/MUSE zoned entry */
			if (deduce_version) {
				deduce_version = 0;
				g_format = F_MUSH;
				g_version = 1;
				deduce_name = 1;
				deduce_zone = 0;
				read_key = 0;
				read_attribs = 0;
			} else if (deduce_zone) {
				deduce_zone = 0;
				read_zone = 1;
				g_flags |= V_ZONE;
			}
		case '!':	/* MUSH entry/MUSE non-zoned entry */
			if (deduce_version) {
				g_format = F_MUSH;
				g_version = 1;
				deduce_name = 0;
				deduce_zone = 0;
				deduce_version = 0;
			} else if (deduce_zone) {
				deduce_zone = 0;
				read_zone = 0;
			}
			i = getref(f);
			db_grow(i+1);
			if (read_name) {
				tstr = getstring_noalloc(f);
				if (deduce_name) {
					if (isdigit(*tstr)) {
						read_name = 0;
						s_Location(i, atol(tstr));
					} else {
						s_Name(i, tstr);
						s_Location(i, getref(f));
					}
					deduce_name = 0;
				} else {
					s_Name(i, tstr);
					s_Location(i, getref(f));
				}
			} else {
				s_Location(i, getref(f));
			}
			if (read_zone)
				s_Zone(i, getref(f));
			else
				s_Zone(i, NOTHING);
			s_Contents(i, getref(f));
			s_Exits(i, getref(f));
			if (read_link)
				s_Link(i, getref(f));
			else
				s_Link(i, NOTHING);
			s_Next(i, getref(f));
			if (read_key) {
				tempbool = getboolexp(f);
				atr_add_raw(i, A_LOCK,
					unparse_boolexp_quiet(1, tempbool));
				free_boolexp(tempbool);
				if (read_pern_key) {

					/* Read Pern 1.17-style locks */

					tempbool = getboolexp(f);
					atr_add_raw(i, A_LUSE,
						unparse_boolexp_quiet(1,
						tempbool));
					free_boolexp(tempbool);
					tempbool = getboolexp(f);
					atr_add_raw(i, A_LENTER,
						unparse_boolexp_quiet(1,
						tempbool));
					free_boolexp(tempbool);
				}
			}

			s_Owner(i, getref(f));
			if (read_parent)
				s_Parent(i, getref(f));
			else
				s_Parent(i, NOTHING);
			if (read_pennies) /* if not fix in unscraw_foreign */
				s_Pennies(i, getref(f));
			s_Flags(i, upgrade_flags(getref(f), i,
				g_format, g_version));
			if (read_powers_any ||
			    ((Typeof(i) == TYPE_PLAYER) && read_powers_player))
				(void)getstring_noalloc(f);
			if (read_pern_comm)
				(void)getref(f);
			if (read_attribs) {
				if (!get_list(f, i)) {
					fprintf(stderr,
						"\nError reading attrs for object #%d\n",
						i);
					return -1;
				}
			}
			if (read_muse_parents) {
				getlist_discard(f);
				getlist_discard(f);
			}
			if (read_muse_atrdefs) {
				get_atrdefs_discard(f);
			}

			/* check to see if it's a player */

			if (Typeof(i) == TYPE_PLAYER) {
				s_Flags(i, Flags(i) & (~PLAYER_CONNECT));
			}
			break;
		case '*':	/* EOF marker */
			tstr = getstring_noalloc(f);
			if (strcmp(tstr, "**END OF DUMP***")) {
				fprintf(stderr,
					"\nBad EOF marker at object #%d\n",
					i);
				return -1;
			} else {
#ifdef STANDALONE
				fprintf(stderr, "\n");
				fflush(stderr);
#endif
				/* Fix up bizarro foreign DBs */

				unscraw_foreign(g_format, g_version, g_flags);
				*db_version = g_version;
				*db_format = g_format;
				*db_flags = g_flags;
#ifndef STANDALONE
				load_player_names();
#endif
				return mudstate.db_top;
			}
		default:
			fprintf(stderr, "\nIllegal character '%c' near object #%d\n",
				ch, i);
			return -1;
		}
	}
}

static int db_write_object(FILE * f, dbref i, int db_format, int flags)
{
ATTR	*a;
char	*got, *as;
dbref	aowner;
int	ca, aflags, save;
struct boolexp *tempbool;

#ifdef ATR_NAME
	if (!(flags & V_ATRNAME))
		putstring(f, Name(i));
#else
	putstring(f, Name(i));
#endif
	putref(f, Location(i));
	if (flags & V_ZONE)
		putref(f, Zone(i));
	putref(f, Contents(i));
	putref(f, Exits(i));
	if (flags & V_LINK)
		putref(f, Link(i));
	putref(f, Next(i));
	if (!(flags & V_ATRKEY)) {
		got = atr_get(i, A_LOCK, &aowner, &aflags);
		tempbool = parse_boolexp(GOD, got);
		free_lbuf(got);
		putboolexp(f, tempbool);
		free_bool(tempbool);
	}
	putref(f, Owner(i));
	if (flags & V_PARENT)
		putref(f, Parent(i));
	putref(f, Pennies(i));
	putref(f, Flags(i));

	/* write the attribute list */

	if (!(flags & V_GDBM)) {
		for (ca=atr_head(i,&as); ca; ca=atr_next(&as)) {
			a = atr_num(ca);
			save = 0;
			if (a) {
				switch (a->number) {
				case A_NAME:
					if (flags & V_ATRNAME)	save = 1;
					break;
				case A_LOCK:
					if (flags & V_ATRKEY)	save = 1;
					break;
				case A_LIST:
					break;
				default:
					save = 1;
				}
			}
			if (save) {
				got = atr_get_raw(i, a->number);
				fprintf(f, ">%d\n%s\n", a->number, got);
			}
		}
		fprintf(f, "<\n");
	}
	return 0;
}

dbref db_write(FILE * f, int format, int version)
{
dbref	i;
int	flags;
VATTR	*vp;

	al_store();
	switch (format) {
	case F_MUSH:
		flags = version;
		break;
	default:
		fprintf(stderr, "Can only write MUSH format.\n");
		return -1;
	}
#ifdef STANDALONE
	fprintf(stderr, "Writing ");
	fflush(stderr);
#endif
	i = mudstate.attr_next;
	fprintf(f, "+V%d\n+S%d\n+N%d\n", flags, mudstate.db_top, i);

	/* Dump user-named attribute info */

	vp = mudstate.user_attrs;
	while (vp != NULL) {
		if (!(vp->flags & AF_DELETED))
			fprintf(f, "+A%d\n%d:%s\n",
				vp->number, vp->flags, vp->name);
		vp = vp->next;
	}

	vp = mudstate.user_attr_free;
	while (vp != NULL) {
		fprintf(f, "+F%d\n", vp->number);
		vp = vp->next;
	}

	DO_WHOLE_DB(i) {
#ifdef STANDALONE
		if (!(i % 100)) {
			fputc('.', stderr);
			fflush(stderr);
			tmp_sync();
			cache_reset();
		}
#endif
		fprintf(f, "!%d\n", i);
		db_write_object(f, i, format, flags);
	}
	fputs("***END OF DUMP***\n", f);
	fflush(f);
#ifdef STANDALONE
	fprintf(stderr, "\n");
	fflush(stderr);
#endif
	return (mudstate.db_top);
}