/
driver3.2@242/autoconf/
driver3.2@242/doc/LPC/
driver3.2@242/hosts/
driver3.2@242/hosts/amiga/NetIncl/
driver3.2@242/hosts/amiga/NetIncl/netinet/
driver3.2@242/hosts/amiga/NetIncl/sys/
driver3.2@242/hosts/atari/
driver3.2@242/hosts/fcrypt/
driver3.2@242/mudlib/
driver3.2@242/mudlib/sys/
driver3.2@242/util/
driver3.2@242/util/indent/hosts/next/
driver3.2@242/util/make_docs/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

#include "lint.h"
#include "comm.h"
#include "interpret.h"
#include "object.h"
#include "sent.h"
#include "config.h"
#include "wiz_list.h"
#include "exec.h"

extern int d_flag;
#ifdef D_FLAG
extern int D_flag;
#endif
extern int total_num_prog_blocks, total_prog_block_size;

extern pid_t getpid();

void remove_swap_file PROT((struct object *));

struct object *previous_ob;
extern struct svalue const0;
extern int trace_level;

int tot_alloc_object, tot_alloc_object_size;

#if defined(MSDOS) || ( defined(atarist) && !defined(MINIX) )
#define MSDOS_FS
#endif

#define SAVE_OBJECT_VERSION '0'
#define CURRENT_VERSION 0
#ifdef FLOAT_FORMAT_0
#define SAVE_OBJECT_HOST '0'
#define CURRENT_HOST 0
#endif
#ifdef FLOAT_FORMAT_1
#define SAVE_OBJECT_HOST '1'
#define CURRENT_HOST 1
#endif

#define SAVE_OBJECT_BUFSIZE 4096

#define MY_PUTC(char) {\
    *buf_pnt++ = char;\
    if (!--  buf_left) {\
	buf_pnt = write_buffer();\
	buf_left = SAVE_OBJECT_BUFSIZE;\
    }\
}

#define L_PUTC_PROLOG register char *l_buf_pnt = buf_pnt;\
		      register int  l_buf_left = buf_left;
#define L_PUTC(char) {\
    *l_buf_pnt++ = char;\
    if (!--l_buf_left) {\
	l_buf_pnt = write_buffer();\
	l_buf_left = SAVE_OBJECT_BUFSIZE;\
    }\
}

#define L_PUTC_EPILOG buf_pnt = l_buf_pnt; buf_left = l_buf_left;

static char save_file_suffix[] = ".o";

static char *save_object_bufstart, *buf_pnt;
static int buf_left;
static int failed;
static int save_object_descriptor;
static int current_id_number; /* This is used to give id numbers to *
			       * shared values in save_object()     */
static int restored_host = -1;

static char *write_buffer()
{
    char *start;

    start = save_object_bufstart;
    if (
      write( save_object_descriptor, start, SAVE_OBJECT_BUFSIZE ) !=
      SAVE_OBJECT_BUFSIZE )
	failed = 1;
    return start;
}

/* save_object has to take care of reused or recursive arrays/mappings.
 *
 * To handle small and large amounts of pointers equally well, which are
 * unpredictable due to the recursive definition of arrays/mappings,
 * a two dimensional hash structure is used, with uninitialized hash tables.
 * A bit vector indicates which entries are used, and which are used for
 * hash tables instead of single entries.
 */

struct pointer_record {
    long key;
    struct pointer_record *next, *next_all;
    long id_number;
    long ref_count;
};

static struct pointer_record *all_pointer_records, **pointer_table;

static char hash_usage[64];

struct sub_table {
    struct pointer_record *records[256];
    char used[32];
    struct sub_table *next_all;
};

static struct sub_table *all_sub_tables;
static char number_buffer[36];

void init_pointer_table(space)
    struct pointer_record **space;
{
    pointer_table = space;
    bzero(hash_usage, sizeof hash_usage);
    all_pointer_records = 0;
    all_sub_tables = 0;
}

void free_pointer_table()
{
    struct pointer_record *record;
    struct sub_table *table;

    for (record = all_pointer_records; record;) {
	struct pointer_record *next = record->next_all;
	xfree((char *)record);
	record = next;
    }
    for (table = all_sub_tables; table;) {
	struct sub_table *next = table->next_all;
	xfree((char *)table);
	table = next;
    }
}

static int recall_pointer(pointer)
    char *pointer;
{
    extern struct vector null_vector;
    long key;
    int hash;
    struct pointer_record *record;

    key = (long)pointer;
    /* we know for sure that we will find the key, because it has been
     * registered before.
     */
    hash = key ^ key >> 16;
    hash ^= hash >> 8;
    hash &= 0xff;
    record = pointer_table[hash];
    if ( hash_usage[1 + (hash >> 2 & ~1)] & 1 << (hash & 7) ) {
	hash = (key ^ key >> 16) & 0xff;
	record = ((struct sub_table *)record)->records[hash];
	while (record->key != key)
	    record = record->next;
    }
    if (!record->ref_count)
	/* Used only once. No need for special treatment. */
	return 0;
    if (pointer == (char*)&null_vector)
	/* Sharing enforced by the game driver */
	return 0;
    {
	int old_id, id;
	char *source, c;
	L_PUTC_PROLOG

	if ( !(old_id = id = record->id_number) ) {
		id = ++current_id_number;
		record->id_number = id;
	}
	L_PUTC('<')
	source = number_buffer;
	(void)sprintf(source, "%d", id);
	c = *source++;
	do L_PUTC(c) while (c = *source++);
	L_PUTC('>')
	if (old_id) {
		/* has been written before */
		L_PUTC_EPILOG
		return 1;
	}
	L_PUTC('=')
	L_PUTC_EPILOG
	return 0;
    }
}

/*
 * Similar to fwrite, but escapes all funny characters;
 * Used by save_object().
 */
static void save_string(src)
register char *src;
{
    register char c;
    L_PUTC_PROLOG

    L_PUTC('\"')
    while (c = *src++) {
	if (isescaped(c)) {
	    switch(c) {
		case '\007': c = 'a'; break;
		case '\b'  : c = 'b'; break;
		case '\t'  : c = 't'; break;
		case '\n'  : c = 'n'; break;
		case '\013': c = 'v'; break;
		case '\014': c = 'f'; break;
		case '\r'  : c = 'r'; break;
	    }
	    L_PUTC('\\')
	}
	L_PUTC(c)
    }
    L_PUTC('\"')
    L_PUTC_EPILOG
}

static void save_svalue PROT((struct svalue *, int));
static void save_array PROT((struct vector *));
static int restore_size PROT((char **str));
INLINE static int restore_array PROT((struct svalue *, char **str));
static int restore_svalue PROT((struct svalue *, char **, int));
int register_pointer PROT((char *));
static void register_array PROT((struct vector *));

#ifdef MAPPINGS

extern void free_mapping PROT((struct mapping*));
void check_map_for_destr PROT((struct mapping *));
extern void walk_mapping PROT((
        struct mapping *,
        void (*)(struct svalue *, struct svalue *, char *),
        char *
));

static void save_mapping_filter(key, data, extra)
    struct svalue *key, *data;
    char *extra;
{
    int i;

    i = (p_int)extra;
    save_svalue(key, i ? ':' : ',' );
    while (--i >= 0)
	save_svalue(data++, i ? ';' : ',' );
}

/*
 * Encode a mapping into a contiguous string.
 */
static void save_mapping(m)
    struct mapping *m;
{
    if ( recall_pointer( (char *)m ) )
        return;
    MY_PUTC('(')
    MY_PUTC('[')
    check_map_for_destr(m);
    walk_mapping(m, save_mapping_filter, (char *)(p_int)m->num_values);
    MY_PUTC(']')
    MY_PUTC(')')
}

struct rms_parameters {
    char *str;
    int num_values;
};

static int restore_map_size(parameters)
    struct rms_parameters *parameters;
{
    char *pt;
    int siz, tsiz;
    int num_values = -1, current_num_values = 0;

    pt = parameters->str; 
    siz = 0;

    if (!pt) return -1;
    while (1) {
	switch (*pt) {
	  case ']':
	  {
	    if (pt[1] != ')') return -1;
	    parameters->str = &pt[2];
	    parameters->num_values = siz ? num_values : 1;
	    return siz;
	  }
	  case '\"':
	  {
	    int backslashes;

	    do {
		pt = strchr(&pt[1],'\"');
		if (!pt) return -1;
		/* the quote is escaped if and only
		 * if the number of slashes is odd. */
		for (backslashes = -1; pt[backslashes] == '\\'; backslashes--) ;
	    } while ( !(backslashes & 1) ) ;
	    pt++;
	    break;
	  }
	  case '(':
	  {
	    parameters->str = pt + 2;
	    if (pt[1] == '{')
		tsiz = restore_size(&parameters->str);
	    else if (pt[1] == '[')
		tsiz = restore_map_size(parameters);
	    else return -1;
	    pt = parameters->str;
	    if (tsiz < 0)
		return -1;
	    break;
	  }
	  case '<':
	  {
	    pt = strchr(pt, '>');
	    if (!pt) return -1;
	    pt++;
	    if (pt[0] == '=') {
		pt++;
		continue;
	    }
	    break;
	  }
	  case '-':
	    pt++;
	    if (!*pt)
		return -1;
	    /* fall through */
	  case '0': case '1': case '2': case '3': case '4':
	  case '5': case '6': case '7': case '8': case '9':
	    if (pt[1] == '.') {
		char *pt2;

		pt2 = strpbrk(pt, "=:;,");
		if (!pt2)
		    return -1;
		if (*pt2 != '=')
		    break;
		pt = strchr(pt2, ':');
		if (!pt) return -1;
		pt++;
	    }
	    /* fall through */
	  default:
	  {
	    pt = strpbrk(pt, ":;,");
	    if (!pt)
		return -1;
	    break;
	  }
	}
	switch (*pt) {
          case ':':
	    if (current_num_values)
		return -1;
	  case ';':
	    current_num_values++;
	    break;
          case ',':
	    siz++;
	    if (current_num_values != num_values) {
		if (num_values >= 0)
		return -1;
		num_values = current_num_values;
	    }
	    current_num_values = 0;
	    break;
	  default:
	    return -1;
	}
	pt++;
    }
    return -1;
}

INLINE static int restore_mapping(svp, str)
    struct svalue *svp;
    char **str;
{
    extern struct mapping *allocate_mapping PROT((int, int));
    struct svalue *get_map_lvalue PROT((struct mapping*, struct svalue*, int));

    struct mapping *z;
    struct svalue key, *data;
    int i;
    char *pt;
    struct rms_parameters tmp_par;
    int siz;

    tmp_par.str = *str;
    siz = restore_map_size(&tmp_par);
    if (siz < 0) {
	*svp = const0;
	return 0;
    }
    z = allocate_mapping(siz, tmp_par.num_values);
    svp->type = T_MAPPING;
    svp->u.map = z;
    while (--siz >= 0) {
	i = tmp_par.num_values;
	key.type = T_NUMBER;
	if (!restore_svalue(&key, str, i ? ':' : ',' )) {
	    free_svalue(&key);
	    return 0;
	}
	data = get_map_lvalue(z, &key, 1);
	free_svalue(&key);
	while (--i >= 0) {
	    if (!restore_svalue(data++, str, i ? ';' : ',' )) return 0;
	}
    }
    pt = *str;
    if (*pt++ != ']' || *pt++ != ')' ) {
	free_mapping(z);
	*svp = const0;
	return 0;
    }
    *str = pt;
    return 1;
}

static void register_mapping PROT((struct mapping *map));

static void register_mapping_filter(key, data, extra)
    struct svalue *key, *data;
    char *extra;
{
    int i;

    if (key->type == T_POINTER) {
	register_array  (key->u.vec);
    } else if (key->type == T_MAPPING) {
	register_mapping(key->u.map);
    }
    for (i = (p_int)extra; --i >= 0; data++) {
	if (data->type == T_POINTER) {
	    register_array  (data->u.vec);
	} else if (data->type == T_MAPPING) {
	    register_mapping(data->u.map);
	}
    }
}

static void register_mapping(map)
    struct mapping *map;
{
    if ( register_pointer( (char *)(map) ) ) return;
    walk_mapping(map, register_mapping_filter, (char *)(p_int)map->num_values);
}

#endif /* MAPPINGS */

/*
 * Encode an element into a contiguous string.
 */
static void save_svalue(v, delimiter)
    struct svalue *v;
    int delimiter;
{
    switch(v->type) {
        case T_STRING:
	    save_string(v->u.string);
	    break;
	case T_POINTER:
	    save_array(v->u.vec);
	    break;
	case T_NUMBER:
	{
	    L_PUTC_PROLOG
	    char *source, c;

	    source = number_buffer;
	    (void)sprintf(source, "%d", v->u.number);
	    c = *source++;
	    do L_PUTC(c) while (c = *source++);
	    L_PUTC(delimiter);
	    L_PUTC_EPILOG
	    return;
	}
	case T_FLOAT:
	{
	    L_PUTC_PROLOG
	    char *source, c;

	    source = number_buffer;
	    (void)sprintf(source, "%.12e=%x:%x",
		READ_DOUBLE(v),
		v->x.exponent & 0xffff,
		v->u.mantissa);
	    c = *source++;
	    do L_PUTC(c) while (c = *source++);
	    L_PUTC(delimiter);
	    L_PUTC_EPILOG
	    return;
	}
#ifdef MAPPINGS
	case T_MAPPING:
	    save_mapping(v->u.map);
	    break;
#endif MAPPINGS
	default:
	{
	    L_PUTC_PROLOG
	    L_PUTC('0');	/* Objects can't be saved. */
	    L_PUTC(delimiter);
	    L_PUTC_EPILOG
	    return;
	}
    }
    MY_PUTC(delimiter);
}

/*
 * Encode an array of elements into a contiguous string.
 */
static void save_array(v)
    struct vector *v;
{
    int i;
    struct svalue *val;
    if ( recall_pointer( (char *)v ) )
        return;
    {
	L_PUTC_PROLOG
	L_PUTC('(')
	L_PUTC('{')
	L_PUTC_EPILOG
    }
    for (i = v->size, val = v->item; --i >= 0; ) {
	save_svalue(val++, ',');
    }
    {
	L_PUTC_PROLOG
	L_PUTC('}')
	L_PUTC(')')
	L_PUTC_EPILOG
    }
}

static void register_array(vec)
    struct vector *vec;
{
    struct svalue *v;
    int i;

    if ( register_pointer( (char *)(vec) ) ) return;
    v = vec->item;
    for (i = vec->size; --i >= 0; v++) {
	if (v->type == T_POINTER) {
	    register_array  (v->u.vec);
	} else if (v->type == T_MAPPING) {
	    register_mapping(v->u.map);
	}
    }
}

long current_pointer_id;
static struct svalue *shared_restored_values;
static long max_shared_restored, current_shared_restored;

int register_pointer(pointer)
char *pointer;
{
    long key;
    int hash, mask;
    char *usage_p;
    struct pointer_record *old, *new, **insert;

    key = (long)pointer;
    hash = key ^ key >> 16;
    hash ^= hash >> 8;
    hash &= 0xff;
    mask = 1 << (hash & 7);
    usage_p = hash_usage + (hash >> 2 & ~1);
    insert = &pointer_table[hash];
    old = 0;
    if (usage_p[0] & mask) switch (0) { default:
	/* The place in the main hash table has been used before */
	if (usage_p[1] & mask) {
	    /* this place is already used for a sub - hash-table. */
	    struct sub_table *table;

	    table = *(struct sub_table**)insert;
	    hash = (key ^ key >> 16) & 0xff;
	    mask = 1 << (hash & 7);
	    usage_p = &table->used[hash >> 3];
	    insert = &table->records[hash];
	    if ( !(usage_p[0] & mask) )
		/* insert in free place */
		break;
	    old = *insert;
	    do {
		if (old->key == key) {
		    old->ref_count++;
		    return 1;
		}
	    } while (old = old->next);
	    old = *insert;
	    /* insert at top of sub hash chain */
	    break;
	} else {
	    struct sub_table *table;
	    int old_hash;

	    old = *insert;
	    if (old->key == key) {
		old->ref_count++;
		return 1;
	    }
	    /* create a new sub - hash-table. */
	    usage_p[1] |= mask;
	    table = (struct sub_table *)xalloc(sizeof *table);
	    *insert = (struct pointer_record *)table;
	    table->next_all = all_sub_tables;
	    all_sub_tables = table;
	    bzero(table->used, sizeof table->used);
	    old_hash = (old->key ^ old->key >> 16) & 0xff;
	    table->used[old_hash >> 3] |= 1 << (old_hash & 7);
	    table->records[old_hash] = old;
	    hash = (key ^ key >> 16) & 0xff;
	    if (hash != old_hash) {
		old = 0;
	    }
	    insert = &table->records[hash];
	    mask = 1 << (hash & 7);
	    usage_p = &table->used[hash >> 3];
	}
    }
    usage_p[0] |= mask;
    new = (struct pointer_record *)xalloc(sizeof *new);
    *insert = new;
    new->key = key;
    new->next = old;
    new->next_all = all_pointer_records;
    new->ref_count = 0;
    new->id_number = 0;
    all_pointer_records = new;
    return 0;
}

/*
 * Save an object to a file.
 * The routine checks with the function "valid_write()" in /obj/master.c
 * to assertain that the write is legal.
 */
void save_object(ob, file)
    struct object *ob;
    char *file;
{
    static char save_object_header[] = {
	'#', SAVE_OBJECT_VERSION, ':', SAVE_OBJECT_HOST, '\n'
    };
    char *name, *tmp_name, save_buffer[SAVE_OBJECT_BUFSIZE];
    struct pointer_record *pointer_table_space[256];
    int len, i;
    int f;
    struct svalue *v;
    struct variable *names;

    if (ob->flags & O_DESTRUCTED)
	return;
    /* COMPAT_MODE stuff moved to master.c by amylaar
     * master.c will access current_object->prog->name instead of
     * current_prog->name , but then, this is probably better in COMPAT_MODE
     * anyway.
     */
    file = check_valid_path(file, ob, "save_object", 1);
    if (file == 0)
	error("Illegal use of save_object()\n");
    len = strlen(file);
    name = alloca(len + (sizeof save_file_suffix) +
		  len + (sizeof save_file_suffix) + 4);
    tmp_name = name + len + sizeof save_file_suffix;
    (void)strcpy(name, file);
#ifndef MSDOS_FS
    (void)strcpy(name+len, save_file_suffix);
#endif
    /*
     * Write the save-files to different directories, just in case
     * they are on different file systems.
     */
    sprintf(tmp_name, "%s.tmp", name);
#ifdef MSDOS_FS
    (void)strcpy(name+len, save_file_suffix);
#endif
    save_object_descriptor =
      f = ixopen3(tmp_name, O_CREAT|O_TRUNC|O_WRONLY, 0640);
    if (f < 0) {
	error("Could not open %s for a save.\n", tmp_name);
    }
    /* identify arrays/mappings that are used more than once.*/
    init_pointer_table(pointer_table_space);
    v = ob->variables;
    names = ob->prog->variable_names;
    for (i = ob->prog->num_variables; --i >= 0; v++, names++) {
	if (names->flags & TYPE_MOD_STATIC)
	    continue;
	if (v->type == T_POINTER) {
	    register_array  (v->u.vec);
	} else if (v->type == T_MAPPING) {
	    register_mapping(v->u.map);
	}
    }
    failed = 0;
    current_id_number = 0;
    save_object_bufstart = save_buffer;
    memcpy(save_buffer, save_object_header, sizeof(save_object_header));
    buf_left = SAVE_OBJECT_BUFSIZE - sizeof(save_object_header);
    buf_pnt = save_buffer + sizeof(save_object_header);
    v = ob->variables;
    names = ob->prog->variable_names;
    for (i = ob->prog->num_variables; --i >= 0; v++, names++) {
	if (names->flags & TYPE_MOD_STATIC)
	    continue;
	{
	    char *var_name, c;
	    L_PUTC_PROLOG

	    var_name = names->name;
	    c = *var_name++;
	    do {
		L_PUTC(c)
	    } while (c = *var_name++);
	    L_PUTC(' ')
	    L_PUTC_EPILOG
	}
	save_svalue(v, '\n');
    }
    free_pointer_table();
    if (
      write(
	save_object_descriptor,
	save_object_bufstart,
	SAVE_OBJECT_BUFSIZE-buf_left
      ) !=  SAVE_OBJECT_BUFSIZE-buf_left ) failed = 1;
    if (failed) {
	(void)close(f);
	unlink(tmp_name);
	add_message("Failed to save to file. Disk could be full.\n");
	return;
    }
    (void)unlink(name);
#if !defined(MSDOS_FS) && !defined(AMIGA)
    if (link(tmp_name, name) == -1)
#else /* MSDOS_FS */
    (void) close(f);
    if (rename(tmp_name,name) < 0)
#endif
    {
	perror(name);
	printf("Failed to link %s to %s\n", tmp_name, name);
	add_message("Failed to save object !\n");
    }
#if !defined(MSDOS_FS) && !defined(AMIGA)
    (void)close(f);
    unlink(tmp_name);
#endif
}

/*
 * Find the size of an array. Return -1 for failure.
 */
static int restore_size(str)
     char **str;
{
  char *pt,*pt2;
  int siz,tsiz;

  pt = *str; 
  siz = 0;

  while ((pt) && (*pt)) {
    if (pt[0] == '}') {
      if (pt[1] != ')') return -1;
      *str = &pt[2];
      return siz;
    }
    if (pt[0] == '\"') {
      int backslashes;

      do {
        pt = strchr(&pt[1],'\"');
        if (!pt) return -1;
        /* the quote is escaped if and only if the number of slashes is odd. */
        for (backslashes = -1; pt[backslashes] == '\\'; backslashes--) ;
      } while ( !(backslashes & 1) ) ;
      if (pt[1] != ',') return -1;
      siz++;
      pt += 2;
    }
    else if (pt[0] == '(') { 
      /* Lazy way of doing it, a bit inefficient */
      struct rms_parameters tmp_par;

      tmp_par.str = pt + 2;
      if (pt[1] == '{')
	  tsiz = restore_size(&tmp_par.str);
#ifdef MAPPINGS
      else if (pt[1] == '[')
	  tsiz = restore_map_size(&tmp_par);
#endif
      else return -1;
      pt = tmp_par.str;
      if (tsiz < 0)
	  return -1;
      pt++;
      siz++;
    }
    else if (pt[0] == '<') {
      pt = strchr(pt, '>');
      if (!pt) return -1;
      if (pt[1] == ',') {
        siz++;
        pt += 2;
      } else if (pt[1] == '=') {
        pt += 2;
      } else return -1;
    }
    else {
    pt2 = strchr(pt, ',');
      if (!pt2)
	  return -1;
      siz++;
      pt = &pt2[1];
    }
  }
  return -1;
}

INLINE static int restore_array(svp, str)
    struct svalue *svp;
    char **str;
{
    struct vector *v;
    char *pt, *end;
    int siz;

    end = *str;
    siz = restore_size(&end);
    if (siz < 0) {
	*svp = const0;
	return 0;
    }
    v = allocate_array(siz);

    /* We need to do this now, so that the value can be used inside. */
    svp->type = T_POINTER;
    svp->u.vec = v;

    for ( svp = v->item; --siz >= 0; svp++) {
	if ( !restore_svalue(svp, str, ',') ) {
	    return 0;
	}
    }
    pt = *str;
    if (*pt++ != '}' || *pt++ != ')' ) {
	return 0;
    }
    *str = pt;
    return 1;
}

static int restore_svalue(svp, pt, delimiter)
    struct svalue *svp;
    char **pt;
    int delimiter;
{
    char *cp;

    switch( *(cp = *pt) ) {
      case '\"':
      {
	char *source, *start, c;

	start = cp;
	source = cp+1;

	for(;;) {
	    if ( !(c = *source++) ) {
		*svp = const0;
		return 0;
	    }
#if 1 /* for compatiblity reasons */
#ifndef MSDOS_FS
	    if (c == '\r')
#else
	    if (c == 30)
#endif
		c = '\n';
#endif
	    if (c == '\\') {
		if ( !(c = *source++) ) {
		    *svp = const0;
	            return 0;	/* String ends with a \\ buggy probably */
		}
		switch(c) {
		    case 'a': c = '\007'; break;
		    case 'b': c = '\b'  ; break;
		    case 't': c = '\t'  ; break;
		    case 'n': c = '\n'  ; break;
		    case 'v': c = '\013'; break;
		    case 'f': c = '\014'; break;
		    case 'r': c = '\r'  ; break;
		}
	    } else if (c == '\"') break;
	    *cp++ = c;
	}
	*cp=0;
	*pt = source;
	svp->type = T_STRING;
	svp->x.string_type = STRING_SHARED;
	svp->u.string = make_shared_string(start);
	break;
      }
      case '(':
#ifdef MAPPINGS
	*pt = cp+2;
	switch ( cp[1] ) {
	  case '[':
	  {
	    if ( !restore_mapping(svp, pt) ) {
		return 0;
	    }
	    break;
	  }
#endif /* MAPPINGS */
	  case '{':
	  {
	    if ( !restore_array(svp, pt) ) {
		return 0;
	    }
	    break;
	  }
	  default:
	    return 0;
	}
	break;
      case '-':
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      {
	char c, *numstart = cp;
	int nega = 0;
	long l = 0;

	if (*cp == '-') {
	    nega = 1;
	    cp++;
	}
	while(lexdigit(c = *cp++)) l = (((l << 2) + l) << 1) + (c - '0');
	if (c != '.') {
	    svp->type = T_NUMBER;
	    svp->u.number = nega ? -l : l;
	    *pt = cp;
	    return c == delimiter;
	}
	svp->type = T_FLOAT;
	if ( (cp = strchr(cp, '=')) &&  restored_host == CURRENT_HOST) {
	    int tmp;

	    cp++;
	    if (sscanf(cp, "%x:%x", &tmp, &svp->u.mantissa) != 2)
		return 0;
	    svp->x.exponent = tmp;
	} else {
	    STORE_DOUBLE_USED
	    double d;

	    d = atof(cp = numstart);
	    STORE_DOUBLE(svp, d);
	}
	cp = strchr(cp, delimiter);
	*pt = cp+1;
	return (int)cp;
      }
      case '<':
      {
	int id;

	id = atoi(cp+1);
	cp = strchr(cp, '>');
	if (!cp) {
	    *svp = const0;
	    return 0;
	}
	if (cp[1] == '=') {
	    int res;

	    *pt = cp+2;

	    /* Shared values can be used even before they have been read in
	     * completely.
	     */
	    if (id != ++current_shared_restored) {
		*svp = const0;
		return 0;
	    }
	    if (id > max_shared_restored) {
		max_shared_restored <<= 1;
		shared_restored_values = (struct svalue *)
		  realloc((char*)shared_restored_values, max_shared_restored);
	    }
	    id--;
	    res = restore_svalue(&shared_restored_values[id], pt, delimiter);
	    *svp = shared_restored_values[id];
	    return res;
	}
	if (id <= 0 || id > current_shared_restored) {
	    *svp = const0;
	    return 0;
	}
	assign_svalue_no_free(svp, &shared_restored_values[id-1]);
	cp = strchr(cp, delimiter);
	*pt = cp+1;
	return (int)cp;
      }
      default:
	*svp = const0;
	return 0;
    }
    cp = *pt;
    if (*cp++ != delimiter) return 0;
    *pt = cp;
    return 1;
}

/* Needed to restore backslashes correctly... */
static int old_restore_string(v, str)
    struct svalue *v;
    char *str;
{
    char *cp, c;

    cp = ++str;
    if (c = *cp++) {
	do {
#ifndef MSDOS_FS
	    if (c == '\r')
#else
	    if (c == 30)
#endif
		cp[-1] = '\n';
	} while (c = *cp++);
	if (cp[-2] == '\n' && cp[-3] == '\"') {
	    cp[-3] = '\0';
	    v->type = T_STRING;
	    v->x.string_type = STRING_SHARED;
	    v->u.string = make_shared_string(str);
	    return 1;
	}
    }
    *v = const0;
    return 0;
}

int restore_object(ob, file)
    struct object *ob;
    char *file;
{
    char *name, *var, *buff, *space;
    int len;
    FILE *f;
    struct stat st;
    struct variable *rover;
    /* rover is used uninitialised, but var_rest is initialised instead. */
    int var_rest, num_var;
    int old_format;
    struct discarded {
	struct svalue v;
	struct discarded *next;
    } * dp = 0;

    if (current_object != ob)
	fatal("Bad argument to restore_object()\n");
    if (ob->flags & O_DESTRUCTED)
	return 0;

    file = check_valid_path(file, ob, "restore_object", 0);
    if (file == 0)
	error("Illegal use of restore_object()\n");

    len = strlen(file);
    name = alloca(len + (sizeof save_file_suffix));
    (void)strcpy(name, file);
    if (name[len-2] == '.' && name[len-1] == 'c')
	len -= 2;
    (void)strcpy(name+len, save_file_suffix);
    f = fopen(name, "r");
    if (!f || fstat(fileno(f), &st) == -1) {
	if (f) 
	    (void)fclose(f);
	return 0;
    }
    if (st.st_size == 0) {
	(void)fclose(f);
	return 0;
    }
    buff = alloca(st.st_size + 1);
    shared_restored_values = (struct svalue *)
      xalloc(sizeof(struct svalue)*256);
    max_shared_restored = 256;
    current_shared_restored = 0;
    num_var = ob->prog->num_variables;
    var_rest = 0;
    old_format = 1;
    restored_host = -1;
    while(1) {
	struct svalue *v;
	char *pt;

	if (fgets(buff, st.st_size + 1, f) == 0)
	    break;
	/* Remember that we have a newline at end of buff ! */
	space = strchr(buff, ' ');
	if (space == 0) {
	    if (buff[0] == '#') {
		int i;
		int restored_version;

		i = sscanf(buff+1, "%d:%d", &restored_version, &restored_host);
		if (i <= 0 || i == 2 && restored_version <= CURRENT_VERSION) {
		    old_format = 0;
		    continue;
		}
	    }
	    (void)fclose(f);
	    if (dp) do free_svalue(&dp->v); while (dp=dp->next);
	    xfree((char*)shared_restored_values);
	    error("Illegal format when restoring %s.\n", name);
	    return 0; /* flow control hint */
	}
	*space = 0;
	do {
	    if ( var = findstring(buff) ) {
		do
		    rover++;
		while (--var_rest > 0 &&
		  (rover->name != var || rover->flags & TYPE_MOD_STATIC) );
		if (var_rest <= 0) {
		    rover = ob->prog->variable_names-1;
		    var_rest = num_var + 1;
		    do
			rover++;
		    while (--var_rest > 0 &&
		      (rover->name != var || rover->flags & TYPE_MOD_STATIC) );
		    if (var_rest > 0)
			continue;
		} else
		    continue;
	    } {
		struct discarded *tmp;

		tmp = dp;
		dp = (struct discarded *)alloca(sizeof(struct discarded));
		dp->next = tmp;
		v = &dp->v;
		v->type = T_NUMBER;
		break;
	    }
	} while ((v = &ob->variables[num_var-var_rest]),MY_FALSE);
	free_svalue(v);
	pt = space+1;
	if ( old_format && pt[0] == '\"' ?
	     !old_restore_string(v, pt)   :
	     !restore_svalue(v, &pt, '\n')	)
	{
	    (void)fclose(f);
	    if (dp) do free_svalue(&dp->v); while (dp=dp->next);
	    xfree((char*)shared_restored_values);
	    error("Illegal format when restoring %s.\n", name);
	    return 0;
	}
    }
    if (dp) do free_svalue(&dp->v); while (dp=dp->next);
    if (d_flag > 1)
	debug_message("Object %s restored from %s.\n", ob->name, name);
    (void)fclose(f);
    xfree((char*)shared_restored_values);
    return 1;
}

void tell_npc(ob, str)
    struct object *ob;
    char *str;
{
    push_volatile_string(str);
    (void)sapply("catch_tell", ob, 1);
}

/*
 * Send a message to an object.
 * If it is an interactive object, it will go to his
 * screen. Otherwise, it will go to a local function
 * catch_tell() in that object. This enables communications
 * between players and NPC's, and between other NPC's.
 */
void tell_object(ob, str)
    struct object *ob;
    char *str;
{
    struct object *save_command_giver;

    if (ob->flags & O_DESTRUCTED)
	return;
    if (ob->interactive) {
	save_command_giver = command_giver;
	command_giver = ob;
	add_message("%s", str);
	command_giver = save_command_giver;
	return;
    }
    tell_npc(ob, str);
}

#ifdef DEBUG
void _free_object(ob, from)
    struct object *ob;
    char *from;
{
    ob->ref--;
    if (d_flag > 1)
	printf("Subtr ref to ob %s: %d (%s)\n", ob->name,
		      ob->ref, from);
    if (ob->ref > 0)
	return;
    if (d_flag)
	printf("free_object: %s.\n", ob->name);
    if (!(ob->flags & O_DESTRUCTED)) {
	/* This is fatal, and should never happen. */
	fatal("FATAL: Object 0x%x %s ref count 0, but not destructed (from %s).\n",
	    ob, ob->name, from);
    }
#else /* DEBUG */

int _free_object(ob)
    struct object *ob;
{
#endif /* DEBUG */
    if (ob->interactive)
	fatal("Tried to free an interactive object.\n");
    /*
     * If the program is freed, then we can also free the variable
     * declarations.
     */
    if (ob->swap_num != -1)
	remove_swap_file(ob);
    if (ob->prog) {
	tot_alloc_object_size -=
	    ob->prog->num_variables * sizeof (struct svalue) +
		sizeof (struct object) - sizeof (struct svalue);
	free_prog(ob->prog, 1);
	ob->prog = 0;
    }
    if (ob->name) {
	if (d_flag > 1)
	    debug_message("Free object %s\n", ob->name);
	if (lookup_object_hash(ob->name) == ob)
	    fatal("Freeing object %s but name still in name table", ob->name);
	xfree(ob->name);
	ob->name = 0;
    }
    tot_alloc_object--;
    xfree((char *)ob);
#ifndef DEBUG
    return 0;
#endif
}

#ifndef add_ref
#ifndef DEBUG
INLINE
#endif
void add_ref(ob, from)
    struct object *ob;
    char *from;
{
    ob->ref++;
#ifdef DEBUG
    if (d_flag > 1)
	printf("Add reference to object %s: %d (%s)\n", ob->name,
	       ob->ref, from);
#endif
}
#endif

/*
 * Allocate an empty object, and set all variables to 0. Note that a
 * 'struct object' already has space for one variable. So, if no variables
 * are needed, we allocate a space that is smaller than 'struct object'. This
 * unused (last) part must of course (and will not) be referenced.
 */
#ifdef INITALIZATION_BY___INIT
struct object *get_empty_object(num_var, variables)
    int num_var;
    struct variable *variables;
#else
struct object *get_empty_object(num_var, variables, initializers)
    int num_var;
    struct variable *variables;
    struct svalue *initializers;
#endif
{
    static struct object NULL_object = {0};
    struct object *ob;
    int size = sizeof (struct object) - sizeof(struct svalue) +
	num_var * sizeof (struct svalue);
    int i;

    tot_alloc_object++;
    tot_alloc_object_size += size;
    ob = (struct object *)xalloc(size);
    /* marion
     * Don't initialize via memset, this is incorrect. E.g. the bull machines
     * have a (char *)0 which is not zero. We have structure assignment, so
     * use it.
     */
    /* amylaar :
     * structure assignment might be nice, but I prefer consistent statistics.
     * Therefore, memcpy() is more appropriate. Besides, portability also
     * requires initializing the NULL_object...
     */
    memcpy((char *)ob, (char *)&NULL_object,
      sizeof(struct object) - sizeof(struct svalue));
    ob->ref = 1;
    ob->swap_num = -1;
#ifdef DEBUG
    ob->extra_num_variables = num_var;
#endif
    for (i = num_var; --i >= 0; ) {
#ifndef INITALIZATION_BY___INIT
	if (variables[i].flags & NAME_INITIALIZED) {
	    assign_svalue_no_free(&ob->variables[i], &initializers[i]);
	} else
#endif
	    ob->variables[i] = const0;
    }
    return ob;
}

#if 0
void dump_all_objects()
{
    struct object *ob;
    FILE *d;

    d = fopen("OBJ_DUMP", "w");
    if (!d) {
        add_message("Couldn't open dump file.\n");
	return;
    }
    add_message("Dumping data to 'OBJ_DUMP'... ");
    for (ob = obj_list; ob; ob = ob->next_all) {
	fprintf(d, "%s (0x%x)  ", ob->name, (unsigned long)ob);
	if (ob->super)
	    fprintf(d, "Super %s (0x%x)", ob->super->name,
	      (unsigned long)ob->super);
	fprintf(d, "\t Ref %2d Enbl_cmd %d Clnd %d Hrt_bt %d lang ref %d swp %d hp %x\n",
		ob->ref, ob->flags & O_ENABLE_COMMANDS, ob->flags & O_CLONE,
		ob->flags & O_HEART_BEAT, ob->prog == 0 ? 1 : ob->prog->ref,
		ob->flags & O_SWAPPED,
		ob->prog->heart_beat);
    }
    fclose(d);
    add_message("DONE\n");
}
#endif

void remove_all_objects() {
    struct object *ob;
    struct svalue v;

    v.type = T_OBJECT;
    while(1) {
	if (obj_list == 0)
	    break;
	ob = obj_list;
	v.u.ob = ob;
	destruct_object(&v);
	if (ob == obj_list)
	    break;
    }
    remove_destructed_objects();
}

#if 0
/*
 * For debugging purposes.
 */
void check_ob_ref(ob, from)
    struct object *ob;
    char *from;
{
    struct object *o;
    int i;

    for (o = obj_list, i=0; o; o = o->next_all) {
	if (o->inherit == ob)
	    i++;
    }
    if (i+1 > ob->ref) {
	fatal("FATAL too many references to inherited object %s (%d) from %s.\n",
	      ob->name, ob->ref, from);
	if (current_object)
	    fprintf(stderr, "current_object: %s\n", current_object->name);
	for (o = obj_list; o; o = o->next_all) {
	    if (o->inherit != ob)
		continue;
	    fprintf(stderr, "  %s\n", ob->name);
	}
    }
}
#endif /* 0 */

static struct object *hashed_living[LIVING_HASH_SIZE];

static int num_living_names, num_searches = 1, search_length = 1;

static int hash_living_name(str)
    char *str;
{
#if 1
    return hashstr(str, 100, LIVING_HASH_SIZE);
#else
    unsigned ret = 0;

    while(*str)
	ret = ret * 2 + *str++;
    return ret % LIVING_HASH_SIZE;
#endif
}

struct object *find_living_object(str, player)
    char *str;
    int player;
{
    struct object **obp, *tmp;
    struct object **hl;
    int mask;

    num_searches++;
    hl = &hashed_living[hash_living_name(str)];
    mask = player ? O_ONCE_INTERACTIVE : O_ENABLE_COMMANDS;
    for (obp = hl; *obp; obp = &(*obp)->next_hashed_living) {
	search_length++;
	if ( !((*obp)->flags & mask) )
	    continue;
	if (strcmp((*obp)->living_name, str) == 0)
	    break;
    }
    if (*obp == 0)
	return 0;
    /* Move the found ob first. */
    if (obp == hl)
	return *obp;
    tmp = *obp;
    *obp = tmp->next_hashed_living;
    tmp->next_hashed_living = *hl;
    *hl = tmp;
    return tmp;
}

void set_living_name(ob, str)
    struct object *ob;
    char *str;
{
    struct object **hl;

    if (ob->flags & O_DESTRUCTED)
	return;
    if (ob->living_name) {
	remove_living_name(ob);
    }
    num_living_names++;
    hl = &hashed_living[hash_living_name(str)];
    ob->next_hashed_living = *hl;
    *hl = ob;
    ob->living_name = make_shared_string(str);
#if 0	/* This is not a pretty way to find out if it is a wizard */
    if (ob->interactive) {
	struct svalue *v;
	
	v = apply("query_level", ob, 0);
	if (v && v->type == T_NUMBER && v->u.number >= 21)
	    ob->flags |= O_IS_WIZARD;
    }
#endif
    return;
}

void remove_living_name(ob)
    struct object *ob;
{
    struct object **hl;

#ifdef MUDWHO
    sendmudwhologout(ob);
#endif
    num_living_names--;
    if (!ob->living_name)
	fatal("remove_living_name: no living name set.\n");
    hl = &hashed_living[hash_living_name(ob->living_name)];
    while(*hl) {
	if (*hl == ob)
	    break;
	hl = &(*hl)->next_hashed_living;
    }
    if (*hl == 0)
	fatal("remove_living_name: Object named %s no in hash list.\n",
	      ob->living_name);
    *hl = ob->next_hashed_living;
    free_string(ob->living_name);
    ob->next_hashed_living = 0;
    ob->living_name = 0;
}

void stat_living_objects() {
    char print_buff[12];

    sprintf(print_buff, "%5.2f", (float)search_length / (float)num_searches);
    add_message("\
Hash table of living objects:\n\
-----------------------------\n\
%d living named objects, average search length:%s\n",
		num_living_names, print_buff);
}

void reference_prog (progp, from)
    struct program *progp;
    char *from;
{
    progp->ref++;
    if (d_flag)
	printf("reference_prog: %s ref %d (%s)\n",
	    progp->name, progp->ref, from);
}

void do_free_sub_strings(num_strings, strings, num_variables, variable_names)
    int num_strings, num_variables;
    char **strings;
    struct variable *variable_names;
{
    int i;

    /* Free all strings */
    for (i=0; i < num_strings; i++)
        free_string(strings[i]);
    /* Free all variable names */
    for (i = num_variables; --i >= 0; ) {
        free_string(variable_names[i].name);
    }
}

/*
 * Decrement reference count for a program. If it is 0, then free the program.
 * The flag free_sub_strings tells if the propgram plus all used strings
 * should be freed. They normally are, except when objects are swapped,
 * as we want to be able to read the program in again from the swap area.
 * That means that strings are not swapped.
 */
void free_prog(progp, free_sub_strings)
    struct program *progp;
    int free_sub_strings;
{
    progp->ref--;
    if (progp->ref > 0)
	return;
    if (d_flag)
	printf("free_prog: %s\n", progp->name);
    if (progp->ref < 0)
	fatal("Negative ref count for prog ref.\n");
    total_prog_block_size -= progp->total_size;
    total_num_prog_blocks -= 1;
    if (free_sub_strings) {
	int i;

	unsigned char *program = progp->program;
	uint32 *functions = progp->functions;
	/* Free all function names. */
	for (i = progp->num_functions; --i >= 0; ) {
            if ( !(functions[i] & NAME_INHERITED) ) {
		char *name;

		memcpy(
		  (char *)&name,
		  program + (functions[i] & FUNSTART_MASK) - 1 - sizeof name,
		  sizeof name
		);
		free_string(name);
	    }
	}
	do_free_sub_strings(
	  progp->num_strings, progp->strings,
	  progp->num_variables, progp->variable_names);
	/* Free all inherited objects */
	for (i=0; i < progp->num_inherited; i++)
	    free_prog(progp->inherit[i].prog, 1);
	xfree(progp->name);
    }
    xfree((char *)progp);
}

void reset_object(ob, arg)
    struct object *ob;
    int arg;
{
    extern int current_time;

    /* Be sure to update time first ! */
    ob->next_reset = current_time + TIME_TO_RESET/2 +
	random_number(TIME_TO_RESET/2);
#ifdef INITIALIZATION_BY___INIT
    if (arg == 0)
	sapply("__INIT", ob, 0);
#endif
#ifndef NATIVE_MODE
    push_number(arg);
    (void)sapply("reset", ob, 1);
#endif
#ifndef COMPAT_MODE
    if (arg == 0) {
	sapply("create", ob, 0);
    }
#ifdef NATIVE_MODE
    else {
	sapply("reset", ob, 0);
    }
#endif /* NATIVE_MODE */
#endif /* COMPAT_MODE */
    ob->flags |= O_RESET_STATE;
}

struct replace_ob *obj_list_replace = (struct replace_ob *)0;

void replace_programs() {
    extern void replace_program_lambda_adjust
	PROT((struct replace_ob *r_ob, struct program *));

    struct replace_ob *r_ob, *r_next;
    int i,j;
    struct svalue *svp;
#ifdef DEBUG
    if (d_flag)
	debug_message("start of replace_programs\n");
#endif
    for (r_ob=obj_list_replace; r_ob; r_ob = r_next) {
	struct program *old_prog;

	if (r_ob->ob->flags & O_SWAPPED)
	    load_ob_from_swap(r_ob->ob);
        i = r_ob->ob->prog->num_variables - r_ob->new_prog->num_variables;
#ifdef DEBUG
	if (d_flag)
	    debug_message("%d less variables\n",i);
#endif
        tot_alloc_object_size -= i * sizeof(struct svalue[1]);
        svp = r_ob->ob->variables;
        j = r_ob->var_offset;
        i -= j;
#ifdef DEBUG
	if (d_flag)
	    debug_message("freeing %d variables:\n",j);
#endif
        while (--j >= 0) free_svalue(svp++);
#ifdef DEBUG
	if (d_flag)
	    debug_message("freed.\n");
#endif
	j = r_ob->new_prog->num_variables;
	memcpy(
	  (char *)r_ob->ob->variables,
	  (char *)svp,
	  j * sizeof(struct svalue[1])
	);
        svp += j;
#ifdef DEBUG
	if (d_flag)
	    debug_message("freeing %d variables:\n",i);
#endif
        while (--i >= 0) free_svalue(svp++);
#ifdef DEBUG
	if (d_flag)
	    debug_message("freed.\n");
#endif
        r_ob->new_prog->ref++;
	old_prog = r_ob->ob->prog;
        r_ob->ob->prog = r_ob->new_prog;
	r_ob->ob->flags |= O_CLONE;
        r_next = r_ob->next;
	if (r_ob->lambda_rpp) {
	    obj_list_replace = r_next;
	    replace_program_lambda_adjust(r_ob, old_prog);
	}
        free_prog(old_prog, 1);
#ifdef DEBUG
	if (d_flag)
	    debug_message("program freed.\n");
#endif
	if (r_ob->ob->shadowing) {
	    /* The master couldn't decide if it's a legal shadowing before
	     * the program was actually replaced. It is possible that the
	     * blueprint to the replacing program is already destructed,
	     * and it's source changed.
	     * On the other hand, if we called the master now, all kind of
	     * volatile data structures could result, even new entries for
	     * obj_list_replace. This would eventually require to reference it,
	     * and all the lrpp's , in check_a_lot_ref_counts() and
	     * garbage_collection() . Being able to use replace_program() in
	     * shadows is hardly worth this effort. Thus, we simply stop
	     * the shadowing.
	     */
	    r_ob->ob->shadowing->shadowed = r_ob->ob->shadowed;
	    if (r_ob->ob->shadowed) {
		r_ob->ob->shadowed->shadowing = r_ob->ob->shadowing;
		r_ob->ob->shadowed = 0;
	    }
	    r_ob->ob->shadowing = 0;
	}
        xfree((char *)r_ob);
    }
    obj_list_replace = (struct replace_ob *)0;
#ifdef DEBUG
    if (d_flag)
	debug_message("end of replace_programs\n");
#endif
}

/*
 * If there is a shadow for this object, then the message should be
 * sent to it. But only if catch_tell() is defined. Beware that one of the
 * shadows may be the originator of the message, which means that we must
 * not send the message to that shadow, or any shadows in the linked list
 * before that shadow.
 */
int shadow_catch_message(ob, str)
    struct object *ob;
    char *str;
{
    if (!ob->interactive->catch_tell_activ || ob == current_object)
	return 0;
    trace_level |= ob->interactive->trace_level;
    push_volatile_string(str);
    if (sapply("catch_tell", ob, 1))
	return 1;
    /* The call failed, thus, current_object wasn't changed
     * (e.g. destructed and set to 0 ) .
     * !current_object is true when a prompt is given.
     */
    if (!current_object || !current_object->shadowing)
	ob->interactive->catch_tell_activ = 0;
    return 0;
}

#if 0
int shadow_catch_message(ob, str)
    struct object *ob;
    char *str;
{
    if (!ob->shadowed)
	return 0;
    while(ob->shadowed != 0 && ob->shadowed != current_object)
	ob = ob->shadowed;
    while(ob->shadowing) {
	if (function_exists("catch_tell", ob))
	{
	    push_volatile_string(str);
	    if (apply("catch_tell", ob, 1)) /* this will work, since we know the */
		/* function is defined */
		return 1;
	}
	ob = ob->shadowing;
    }
    return 0;
}
#endif /* 0 */