/***************************************************************************
 savegame.cfsml Copyright (C) 1999 Christoph Reichenbach, TU Darmstadt


 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.


 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

    Christoph Reichenbach (CJR) [jameson@linuxgames.com]

***************************************************************************/
/* Savegame handling for state_t structs. Makes heavy use of cfsml magic. */
/* DON'T EDIT savegame.c ! Only modify savegame.cfsml, if something needs
** to be changed. Refer to freesci/docs/misc/cfsml.spec if you don't understand
** savegame.cfsml. If this doesn't solve your problem, contact the maintainer.
*/

#include <sci_memory.h>
#include <gfx_operations.h>
#include <engine.h>
#include <assert.h>
#include <heap.h>

#ifdef _MSC_VER
#  include <direct.h>
#endif

#ifdef _WIN32
#pragma warning( disable : 4101 )
#endif

#ifdef _DREAMCAST
#  include <dc.h>
#endif

#define HUNK_TYPE_GFX_SNAPSHOT_STRING "g\n"

/* Missing:
** - SFXdriver
** - File input/output state (this is likely not to happen)
*/

static state_t *_global_save_state;
/* Needed for some graphical stuff. */
#define FILE_VERSION _global_save_state->savegame_version


void
write_heapptr(FILE *fh, heap_ptr *foo)
{
	fprintf(fh, "0x%04x", *foo);
}

int
read_heapptr(FILE *fh, heap_ptr *foo, char *lastval, int *line, int *hiteof)
{
	*foo = strtol(lastval, NULL, 0);
	return 0;
}

void
write_sci_version(FILE *fh, sci_version_t *foo)
{
	fprintf(fh, "%d.%03d.%03d", SCI_VERSION_MAJOR(*foo), SCI_VERSION_MINOR(*foo),
		SCI_VERSION_PATCHLEVEL(*foo));
}

int
read_sci_version(FILE *fh, sci_version_t *foo, char *lastval, int *line, int *hiteof)
{
	*foo = version_parse(lastval);
	return 0;
}

void
write_PTN(FILE *fh, parse_tree_node_t *foo)
{
	if (foo->type == PARSE_TREE_NODE_LEAF)
		fprintf(fh, "L%d", foo->content.value);
	else
		fprintf(fh, "B(%d,%d)", foo->content.branches[0], foo->content.branches[1]);
}

int
read_PTN(FILE *fh, parse_tree_node_t *foo, char *lastval, int *line, int *hiteof)
{
	if (lastval[0] == 'L') {
		char *c = lastval + 1;
		char *strend;

		while (*c && isspace(*c))
			++c;

		if (!*c)
			return 1;

		foo->content.value = strtol(c, &strend, 0);

		return (strend == c); /* Error if nothing could be read */

		return 0;
	} else if (lastval[0] == 'B') {
		char *c = lastval + 1;
		char *strend;

		while (*c && isspace(*c)) ++c;
		if (*c++ != '(') return 1;
		while (*c && isspace(*c)) ++c;

		foo->content.branches[0] = strtol(c, &strend, 0);
		if (strend == c)
			return 1;
		c = strend;

		while (*c && isspace(*c)) ++c;
		if (*c++ != ',')
			return 1;

		while (*c && isspace(*c)) ++c;

		foo->content.branches[1] = strtol(c, &strend, 0);
		if (strend == c)
			return 1;
		c = strend;

		while (*c && isspace(*c)) ++c;
		if (*c++ != ')') return 1;

		return 0;
	} else return 1; /* failure to parse anything */
}


void
write_menubar_tp(FILE *fh, menubar_t **foo);
int
read_menubar_tp(FILE *fh, menubar_t **foo, char *lastval, int *line, int *hiteof);

void
write_any_widget(FILE *fh, gfxw_widget_t **widget);
int
read_any_widget(FILE *fh, gfxw_widget_t **widget, char *lastval, int *line, int *hiteof);

void
write_pixmap_color(FILE *fh, gfx_pixmap_color_t *color);
int
read_pixmap_color(FILE *fh, gfx_pixmap_color_t *color, char *lastval, int *line, int *hiteof);

void
write_hunk_block(FILE *fh, hunk_block_t *foo);
int
read_hunk_block(FILE *fh, hunk_block_t *foo, char *lastval, int *line, int *hiteof);

%CFSML

TYPE byte "byte" LIKE int;
TYPE long "long" LIKE int;
TYPE gint16 "gint16" LIKE int;
TYPE heap_ptr "heap_ptr" USING write_heapptr read_heapptr;
TYPE sci_version_t "sci_version_t" USING write_sci_version read_sci_version;

TYPE hunk_block_t "hunk_block_t" USING write_hunk_block read_hunk_block;
TYPE parse_tree_node_t "parse_tree_node_t" USING write_PTN read_PTN;

TYPE menubar_tp "menubar_t*" USING write_menubar_tp read_menubar_tp;

/* Widget subsystem */
/*------------------*/
TYPE any_widget_p "gfxw_widget_t*" USING write_any_widget read_any_widget;
TYPE buffered_gfx_pixmap_color_t "gfxw_pixmap_color_t" USING write_pixmap_color read_pixmap_color;
TYPE gfxw_widget_types_t "gfxw_widget_types_t" LIKE int;
TYPE gfx_line_mode_t "gfx_line_mode_t" LIKE int;
TYPE gfx_line_style_t "gfx_line_style_t" LIKE int;
TYPE gfx_box_shade_t "gfx_box_shade_t" LIKE int;
TYPE gfx_alignment_t "gfx_alignment_t" LIKE int;

RECORD point_t {
	int x;
	int y;
}

RECORD rect_t EXTENDS point_t {
	int xl;
	int yl;
}

RECORD gfx_pixmap_color_t {
	byte r;
	byte g;
	byte b;
}

RECORD gfx_color_t {
	buffered_gfx_pixmap_color_t visual;
	byte alpha;
	byte priority;
	byte control;
	byte mask;
}

RECORD gfx_dirty_rect_t {
	rect_t rect;
	gfx_dirty_rect_t * next;
}


RECORD gfxw_widget_t {
	int magic;
	int serial;
	int flags;
	gfxw_widget_types_t type;
	rect_t bounds;
	any_widget_p next;
	int ID;
	int widget_priority;
}
/* Handled externally: parent, visual */

RECORD gfxw_box_t EXTENDS gfxw_widget_t {
	gfx_color_t color1;
	gfx_color_t color2;
	gfx_box_shade_t shade_type;
}

RECORD gfxw_primitive_t EXTENDS gfxw_widget_t {
	gfx_color_t color;
	gfx_line_mode_t line_mode;
	gfx_line_mode_t line_style;
}

RECORD gfxw_view_t EXTENDS gfxw_widget_t {
	point_t pos;
	gfx_color_t color;
	int view;
	int loop;
	int cel;
}

RECORD gfxw_dyn_view_t EXTENDS gfxw_view_t {
	rect_t draw_bounds;
	int under_bitsp;
	int signalp;
	int under_bits;
	int signal;
	int z;
	int sequence;
	int force_precedence;
}

RECORD gfxw_text_t EXTENDS gfxw_widget_t {
	int font_nr;
	string text;
	gfx_alignment_t halign;
	gfx_alignment_t valign;
	gfx_color_t color1;
	gfx_color_t color2;
	gfx_color_t bgcolor;
	int text_flags;
	int width;
	int height;
}
/* Handled externally: text_handle */


RECORD gfxw_container_t EXTENDS gfxw_widget_t {
	rect_t zone;
	gfx_dirty_rect_t * dirty;
	any_widget_p contents;
}
/* Handled externally: nextpp */

RECORD gfxw_list_t "gfxw_list_t" EXTENDS gfxw_container_t {
}


RECORD gfxw_visual_t EXTENDS gfxw_container_t {
	int port_refs_nr;
	int font_nr;
}
/* Handled externally: gfx_state, port_refs */

RECORD gfxw_port_t EXTENDS gfxw_container_t {
	any_widget_p decorations;
	any_widget_p port_bg;
	gfx_color_t color;
	gfx_color_t bgcolor;
	int font_nr;
	point_t draw_pos;
	int port_flags;
	string title_text;
	byte gray_text;
}


RECORD gfxw_snapshot_t "gfxw_snapshot_t" {
	int serial;
	rect_t area;
}


/*-------------------------*/
/* End of widget subsystem */

RECORD menu_item_t "menu_item_t" {
	int type;
	string keytext;
	int keytext_size;

	int flags;
	byte said[STATIC MENU_SAID_SPEC_SIZE];
	heap_ptr said_pos;
	string text;
	heap_ptr text_pos;
	int modifiers;
	int key;
	int enabled;
	int tag;
}

RECORD menu_t "menu_t" {
	string title;
	int title_width;
	int width;

	menu_item_t items[DYNAMIC items_nr];
}

RECORD menubar_t "menubar_t" {
	menu_t menus[DYNAMIC menus_nr];
}

RECORD exec_stack_t "exec_stack_t" {
	heap_ptr objp;
	heap_ptr sendp;
	heap_ptr pc;
	heap_ptr sp;
	int argc;
	heap_ptr variables[STATIC 4];
	int selector;
	int origin;
	heap_ptr type;
}

RECORD class_t "class_t" {
	int script;
	int class_offset;
}

RECORD view_object_t {
	heap_ptr obj;
	heap_ptr signalp;
	heap_ptr underBitsp;
	int x;
	int y;
	int priority;
	int view_nr;
	int loop;
	int cel;
	int nsTop;
	int nsLeft;
	int nsRight;
	int nsBottom;
	int underBits;
}


RECORD script_t "script_t" {
	heap_ptr heappos;
	heap_ptr localvar_offset;
	heap_ptr export_table_offset;
	heap_ptr synonyms_offset;
	int lockers;
	int synonyms_nr;
}


RECORD synonym_t "synonym_t" {
	int replaceant;
	int replacement;
}


RECORD drawn_pic_t "drawn_pic_t" {
	int nr;
	int palette;
}

RECORD state_t "state_t" {
	int savegame_version;

	byte restarting_flags;
	byte have_mouse_flag;
	byte pic_not_valid;
	byte pic_is_new;
	byte onscreen_console;

	long game_time;
	heap_ptr save_dir;
	heap_ptr save_dir_copy;
	string save_dir_copy_buf;
	int save_dir_edit_offset;
	heap_ptr sound_object;

	int sound_mute;
	int sound_volume;

	int mouse_pointer_nr;

	int port_serial;

	int dyn_views_list_serial;
	int drop_views_list_serial;

	any_widget_p visual;

	int pic_visible_map;
	int pic_animate;

	long animation_delay;

	hunk_block_t hunk[STATIC MAX_HUNK_BLOCKS];

	menubar_tp menubar;
	string status_bar_text;
	int status_bar_foreground;
	int status_bar_background;

	int priority_first;
	int priority_last;

	drawn_pic_t pics[DYNAMIC pics_nr, MAXWRITE pics_drawn_nr];

	byte version_lock_flag;
	sci_version_t version;
	sci_version_t max_version;
	sci_version_t min_version;

	exec_stack_t execution_stack[DYNAMIC execution_stack_size, MAXWRITE execution_stack_pos+1];

	gint16 acc;
	gint16 amp_rest;
	gint16 prev;

	heap_ptr stack_base;
	heap_ptr stack_handle;
	heap_ptr parser_base;
	heap_ptr parser_event;
	heap_ptr global_vars;

	int parser_lastmatch_word;

	parse_tree_node_t parser_nodes[STATIC VOCAB_TREE_NODES];
	int parser_valid;

	synonym_t synonyms[DYNAMIC synonyms_nr];

	heap_ptr game_obj;

	class_t classtable[DYNAMIC classtable_size];

	script_t scripttable[STATIC 1000];

	heap_ptr clone_list[STATIC SCRIPT_MAX_CLONES];

	int _heap->first_free;
	int _heap->old_ff;

	POINTER _heap->base RELATIVETO _heap->start;
	POINTER game_name RELATIVETO _heap->start;


	/* Backwards compatibility crap */
	int port_ID;
	gfx_color_t ega_colors[16];
}

%END CFSML

void
write_hunk_block(FILE *fh, hunk_block_t *foo)
{
	char filename[14] = "hunk.0";
	int filenr = 0;
	int hunkfile;

	if (!foo->size) {
		fputs("\\null\\", fh);
		return; /* Don't write empty blocks */
	}

	if (foo->type == HUNK_TYPE_GFXBUFFER) {
		fputs(HUNK_TYPE_GFX_SNAPSHOT_STRING, fh);
		%CFSMLWRITE gfxw_snapshot_t (*(((gfxw_snapshot_t**)foo->data))) INTO fh;
		return;

	} else { /* Normal buffer */

		while (IS_VALID_FD(hunkfile = open(filename, O_RDONLY | O_BINARY))) {
			close(hunkfile);
			sprintf(filename + 5, "%d", ++filenr);
		}


		hunkfile = sci_create_file(filename);

		assert (IS_VALID_FD(hunkfile));

		write(hunkfile, foo->data, foo->size);

		close(hunkfile);
	}

	fputs(filename, fh);
}

int
read_hunk_block(FILE *fh, hunk_block_t *foo, char *lastval, int *line, int *hiteof)
{
	int hunkfile;

	if (lastval[0] == '\\') { /* Nothing written? */
		foo->size = 0;
		return 0;
	}

	if (lastval[0] == HUNK_TYPE_GFX_SNAPSHOT_STRING[0]) { /* Graphical buffer */
		foo->type = HUNK_TYPE_GFXBUFFER;
		foo->size = sizeof(gfxw_snapshot_t);
		foo->data = sci_malloc(sizeof (gfxw_snapshot_t *));
		*((gfxw_snapshot_t **)foo->data) = sci_malloc(sizeof (gfxw_snapshot_t));

		%CFSMLREAD gfxw_snapshot_t (*(gfxw_snapshot_t**)(foo->data)) FROM fh ERRVAR *hiteof LINECOUNTER *line

		return 0;

	} else { /* Normal hunk */

		foo->type = HUNK_TYPE_ANY;

		hunkfile = open(lastval, O_RDONLY | O_BINARY);

		foo->size = lseek(hunkfile, 0, SEEK_END);
		lseek(hunkfile, 0, SEEK_SET);

		foo->data = (char *) sci_malloc(foo->size);
		read(hunkfile, foo->data, foo->size);
		close(hunkfile);

		return 0;
	}

	return 0;
}

struct {
	gfxw_widget_types_t type;
	char *name;
} widget_string_names[] = {
	{GFXW_BOX, "BOX"},
	{GFXW_RECT, "PRIMITIVE"},
	{GFXW_LINE, "PRIMITIVE"},
	{GFXW_INVERSE_LINE, "PRIMITIVE"},
	{GFXW_STATIC_VIEW, "VIEW"},
	{GFXW_VIEW, "VIEW"},
	{GFXW_DYN_VIEW, "DYNVIEW"},
	{GFXW_PIC_VIEW, "PICVIEW"},
	{GFXW_TEXT, "TEXT"},
	{GFXW_SORTED_LIST, "LIST"},
	{GFXW_LIST, "LIST"},
	{GFXW_VISUAL, "VISUAL"},
	{GFXW_PORT, "PORT"},
	{GFXW_CONTAINER, "_CONTAINER"},
	{GFXW_, "_WIDGET"},
	{-1, NULL}
};

static char *
stringify_widget_type(int type)
{
	int i = 0;
	while (widget_string_names[i].type != type
	       && widget_string_names[i].name)
		i++;

	return widget_string_names[i].name;
}

static gfxw_widget_types_t
parse_widget_type(char *string) /* Rather slow, but WTF */
{
	int i = 0;
	while (widget_string_names[i].name
	       && strcmp(widget_string_names[i].name, string))
		i++;

	return widget_string_names[i].type;
}

void
write_any_widget(FILE *fh, gfxw_widget_t **widget)
{
	char *type_name;

	if (*widget == NULL) {
		fputs("\\null\\", fh);
		return;
	}

	type_name = stringify_widget_type((*widget)->type);
	if (type_name)
		fputs(type_name, fh);
	else {
		sciprintf("While writing widget: Encountered invalid widget type %d\n",
			  (*widget)->type);
		fputs("\\null\\", fh);
		return;
	}

	fputs("\n<\n", fh);

	switch ((*widget)->type) {

	case GFXW_BOX:
		%CFSMLWRITE gfxw_box_t ((gfxw_box_t*)*widget) INTO fh;
		break;

	case GFXW_RECT:
	case GFXW_LINE:
	case GFXW_INVERSE_LINE:
		%CFSMLWRITE gfxw_primitive_t ((gfxw_primitive_t*)*widget) INTO fh;
		break;

	case GFXW_VIEW:
	case GFXW_STATIC_VIEW:
		%CFSMLWRITE gfxw_view_t ((gfxw_view_t*)*widget) INTO fh;
		break;

	case GFXW_DYN_VIEW:
	case GFXW_PIC_VIEW:
		%CFSMLWRITE gfxw_dyn_view_t ((gfxw_dyn_view_t*)*widget) INTO fh;
		break;

	case GFXW_TEXT:
		%CFSMLWRITE gfxw_text_t ((gfxw_text_t*)*widget) INTO fh;
		break;


	case GFXW_SORTED_LIST:
	case GFXW_LIST:
		%CFSMLWRITE gfxw_list_t ((gfxw_list_t*)*widget) INTO fh;
		break;

	case GFXW_VISUAL:
		%CFSMLWRITE gfxw_visual_t ((gfxw_visual_t*)*widget) INTO fh;
		break;

	case GFXW_PORT:
		%CFSMLWRITE gfxw_port_t ((gfxw_port_t*)*widget) INTO fh;
		break;

	case GFXW_:
	case GFXW_CONTAINER:
	default:
		sciprintf("While writing widget: Invalid widget type while writing widget:\n");
		(*widget)->print((*widget), 0);

		if ((*widget)->type == GFXW_CONTAINER) {
			%CFSMLWRITE gfxw_container_t ((gfxw_container_t*)*widget) INTO fh;
		} else {
			%CFSMLWRITE gfxw_widget_t (*widget) INTO fh;
		}
		break;
	}
	fputs(">\n", fh);
}

void
_gfxw_set_ops_BOX(gfxw_widget_t *prim);
void
_gfxw_set_ops_RECT(gfxw_widget_t *prim);
void
_gfxw_set_ops_LINE(gfxw_widget_t *prim);
void
_gfxw_set_ops_VIEW(gfxw_widget_t *view, char stat);
void
_gfxw_set_ops_DYNVIEW(gfxw_widget_t *widget);
void
_gfxw_set_ops_PICVIEW(gfxw_widget_t *widget);
void
_gfxw_set_ops_TEXT(gfxw_widget_t *widget);
void
_gfxw_set_ops_LIST(gfxw_container_t *widget, char sorted);
void
_gfxw_set_ops_VISUAL(gfxw_container_t *visual);
void
_gfxw_set_ops_PORT(gfxw_container_t *widget);


void
full_widget_tree_traversal(gfxw_widget_t *widget, gfxw_container_t *parent, gfxw_visual_t *visual)
{
	if (!widget)
		return;

	widget->parent = parent;

	if (GFXW_IS_VISUAL(widget)) {
		visual = (gfxw_visual_t *) widget;
		visual->gfx_state = _global_save_state->gfx_state;

		if (visual->port_refs_nr < 1)
			sciprintf("visual->port_refs_nr is too small: %d!\n", visual->port_refs_nr);

		visual->port_refs = sci_calloc(sizeof(gfxw_port_t *), visual->port_refs_nr);
	}

	if (widget->next)
		full_widget_tree_traversal(widget->next, parent, visual);

	widget->visual = visual;

	if (GFXW_IS_CONTAINER(widget)) {
		gfxw_container_t *container = GFXWC(widget);

		full_widget_tree_traversal(container->contents, container, visual);

		if (!container->contents)
			container->nextpp = &(container->next);
		else {
			gfxw_widget_t *next_seeker = container->contents;

			while (next_seeker->next)
				next_seeker = next_seeker->next;

			container->nextpp = &(next_seeker->next);
		}

		if (GFXW_IS_PORT(container)) {
			gfxw_port_t *port = (gfxw_port_t *) container;

			full_widget_tree_traversal(GFXW(port->decorations), container, visual);
			full_widget_tree_traversal(port->port_bg, container, visual);

			if (visual->port_refs_nr <= port->ID
			    || 0 > port->ID)
				sciprintf("Restored port with invalid ID #%d\n", port->ID);
			else
				visual->port_refs[port->ID] = port; /* List port globally */

		}
	}

	switch (widget->type) {

	case GFXW_BOX:
		_gfxw_set_ops_BOX(widget);
		break;

	case GFXW_RECT:
		_gfxw_set_ops_RECT(widget);
		break;

	case GFXW_LINE:
	case GFXW_INVERSE_LINE:
		_gfxw_set_ops_LINE(widget);
		break;

	case GFXW_VIEW:
		_gfxw_set_ops_VIEW(widget, 0);
		break;

	case GFXW_STATIC_VIEW:
		_gfxw_set_ops_VIEW(widget, 1);
		break;

	case GFXW_DYN_VIEW:
		_gfxw_set_ops_DYNVIEW(widget);
		break;

	case GFXW_PIC_VIEW:
		_gfxw_set_ops_PICVIEW(widget);
		break;

	case GFXW_TEXT:
		((gfxw_text_t *) widget)->text_handle = NULL;
		_gfxw_set_ops_TEXT(widget);
		break;

	case GFXW_LIST:
		if (widget->serial == _global_save_state->drop_views_list_serial)
			_global_save_state->drop_views = (gfxw_list_t *) widget;
		_gfxw_set_ops_LIST(GFXWC(widget), 0);
		break;

	case GFXW_SORTED_LIST:
		_gfxw_set_ops_LIST(GFXWC(widget), 1);
		if (widget->serial == _global_save_state->dyn_views_list_serial)
			_global_save_state->dyn_views = (gfxw_list_t *) widget;
		break;

	case GFXW_VISUAL:
		_gfxw_set_ops_VISUAL(GFXWC(widget));
		break;

	case GFXW_PORT:
		if (widget->serial == _global_save_state->port_serial)
			_global_save_state->port = (gfxw_port_t *) widget;
		_gfxw_set_ops_PORT(GFXWC(widget));
		break;
	}
}

gfxw_widget_t *
_gfxw_new_widget(int size, int type);

int
read_any_widget(FILE *fh, gfxw_widget_t **widget, char *lastval, int *line, int *hiteof)
{
	gfxw_widget_types_t expected_type;
	int nextchar;

	if (!strcmp(lastval, "\\null\\")) {
		*widget = NULL;
		return 0;
		/* That was easy */
	}

	expected_type = parse_widget_type(lastval);

	if (expected_type == -1) {
		sciprintf("Invalid widget type \"%s\" specified in line %d\n",
			  lastval, *line);
		return 1;
	}

	while ((nextchar = fgetc(fh)) != '<') {
		if (nextchar == EOF)
			return ((*hiteof = 1));

		if (nextchar == '\n')
			(*line)++;
		else if (!isspace(nextchar)) {
			sciprintf("Invalid character encountered: '%c' (%02x) in line %d\n",
				  nextchar, nextchar, *line);
			return 1;
		}
	}

	switch (expected_type) {

	case GFXW_BOX:
		*widget = _gfxw_new_widget(sizeof(gfxw_box_t), expected_type);
		%CFSMLREAD gfxw_box_t ((gfxw_box_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		break;

	case GFXW_RECT:
	case GFXW_LINE:
	case GFXW_INVERSE_LINE:
		*widget = _gfxw_new_widget(sizeof(gfxw_primitive_t), expected_type);
		%CFSMLREAD gfxw_primitive_t ((gfxw_primitive_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		break;

	case GFXW_VIEW:
	case GFXW_STATIC_VIEW:
		*widget = _gfxw_new_widget(sizeof(gfxw_view_t), expected_type);
		%CFSMLREAD gfxw_view_t ((gfxw_view_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		break;

	case GFXW_DYN_VIEW:
	case GFXW_PIC_VIEW:
		*widget = _gfxw_new_widget(sizeof(gfxw_dyn_view_t), expected_type);
		%CFSMLREAD gfxw_dyn_view_t ((gfxw_dyn_view_t*)*widget) FROM fh ERRVAR *hiteof LINECOUNTER *line;
		(*widget)->type = expected_type;
		if (FILE_VERSION == 1)
			((gfxw_dyn_view_t *) widget)->force_precedence = 0;
		break;

	case GFXW_TEXT:
		*widget = _gfxw_new_widget(sizeof(gfxw_text_t), expected_type);
		%CFSMLREAD gfxw_text_t ((gfxw_text_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		(*widget)->type = expected_type;
		break;


	case GFXW_LIST:
	case GFXW_SORTED_LIST:
		*widget = _gfxw_new_widget(sizeof(gfxw_list_t), expected_type);
		GFXWC(*widget)->contents = NULL;
		GFXWC(*widget)->dirty = NULL;
		%CFSMLREAD gfxw_list_t ((gfxw_list_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		(*widget)->type = expected_type;
		break;

	case GFXW_VISUAL:
		*widget = _gfxw_new_widget(sizeof(gfxw_visual_t), expected_type);
		GFXWC(*widget)->contents = NULL;
		GFXWC(*widget)->dirty = NULL;
		%CFSMLREAD gfxw_visual_t ((gfxw_visual_t*)*widget) FROM fh ERRVAR *hiteof LINECOUNTER *line;
		(*widget)->type = expected_type;

		full_widget_tree_traversal(*widget, NULL, NULL);
		break;

	case GFXW_PORT:
		*widget = _gfxw_new_widget(sizeof(gfxw_port_t), expected_type);
		GFXWC(*widget)->contents = NULL;
		GFXWC(*widget)->dirty = NULL;
		((gfxw_port_t *) (*widget))->title_text = NULL;
		((gfxw_port_t *) (*widget))->decorations = NULL;
		((gfxw_port_t *) (*widget))->port_bg = NULL;
		%CFSMLREAD gfxw_port_t ((gfxw_port_t*)*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		(*widget)->type = expected_type;
		break;

	case GFXW_CONTAINER:
		*widget = _gfxw_new_widget(sizeof(gfxw_container_t), expected_type);
		GFXWC(*widget)->contents = NULL;
		GFXWC(*widget)->dirty = NULL;
		sciprintf("Warning: Restoring untyped widget container\n");
		%CFSMLREAD gfxw_container_t ((gfxw_container_t*)*widget) FROM fh ERRVAR *hiteof LINECOUNTER *line;
		(*widget)->type = expected_type;
		break;

	case GFXW_:
		*widget = _gfxw_new_widget(sizeof(gfxw_widget_t), expected_type);
		sciprintf("Warning: Restoring untyped widget\n");
		%CFSMLREAD gfxw_widget_t (*widget) FROM fh ERRVAR *hiteof  LINECOUNTER *line;
		(*widget)->type = expected_type;
		break;

	}

	(*widget)->flags |= GFXW_FLAG_DIRTY;

	while ((nextchar = fgetc(fh)) != '>') {
		if (nextchar == EOF)
			return ((*hiteof = 1));

		if (nextchar == '\n')
			(*line)++;
		else if (!isspace(nextchar)) {
			sciprintf("Invalid character encountered: '%c' (%02x) in line %d\n",
				  nextchar, nextchar, *line);
			return 1;
		}
	}

	return (*hiteof);
}
/* Identify, handle stuff listed as "handled externally" */

void
write_pixmap_color(FILE *fh, gfx_pixmap_color_t *color)
{
	%CFSMLWRITE gfx_pixmap_color_t (color) INTO fh;
}

int
read_pixmap_color(FILE *fh, gfx_pixmap_color_t *color, char *lastval, int *line, int *hiteof)
{
	%CFSMLREAD gfx_pixmap_color_t (color) FROM fh ERRVAR *hiteof FIRSTTOKEN lastval LINECOUNTER *line;

	color->global_index = GFX_COLOR_INDEX_UNMAPPED;

	if (_global_save_state->gfx_state->driver->mode->palette)
		if (gfx_alloc_color(_global_save_state->gfx_state->driver->mode->palette, color) < 0)
			sciprintf("While restoring color index: Allocating color entry for (%02x,%02x,%02x) failed!\n",
				  color->r, color->g, color->b);

	return *hiteof;
}



void
write_menubar_tp(FILE *fh, menubar_t **foo)
{
	if (*foo) {

		%CFSMLWRITE menubar_t (*foo) INTO fh;

	} else { /* Nothing to write */
		fputs("\\null\\", fh);
	}
}


int
read_menubar_tp(FILE *fh, menubar_t **foo, char *lastval, int *line, int *hiteof)
{

	if (lastval[0] == '\\') {
		*foo = NULL; /* No menu bar */
	} else {

		*foo = (menubar_t *) sci_malloc(sizeof(menubar_t));
		%CFSMLREAD menubar_t (*foo) FROM fh ERRVAR *hiteof FIRSTTOKEN lastval LINECOUNTER *line;

	}
	return *hiteof;
}



/* This function is called to undo some strange stuff done in preparation
** to writing a gamestate to disk
*/
void
_gamestate_unfrob(state_t *s)
{ /* Remnant of ancient times. May be restored to its full glory one day. */
	s->heap = s->_heap->start;
}


int
gamestate_save(state_t *s, char *dirname)
{
	FILE *fh;
	sci_dir_t dir;
	char *filename;
	int fd;

	_global_save_state = s;
	s->savegame_version = FREESCI_SAVEGAME_VERSION;
	s->dyn_views_list_serial = (s->dyn_views)? s->dyn_views->serial : -2;
	s->drop_views_list_serial = (s->drop_views)? s->drop_views->serial : -2;
	s->port_serial = (s->port)? s->port->serial : -2;

	if (s->execution_stack_base) {
		sciprintf("Cannot save from below kernel function\n");
		return 1;
	}

	scimkdir (dirname, 0700);

	if (chdir (dirname)) {
		sciprintf("Could not enter directory '%s'\n", dirname);
		return 1;
	}

#ifdef _DREAMCAST
	dc_delete_save_files("/ram");
#else /* !_DREAMCAST */
	sci_init_dir(&dir);
	filename = sci_find_first(&dir, "*");
	while (filename) {
		if (strcmp(filename, "..") && strcmp(filename, "."))
			if(unlink(filename)) { /* Delete all files in directory */
				sciprintf("While preparing savegame: Failed to delete '%s'", filename);
				return 1;
			}
		filename = sci_find_next(&dir);
	}
	sci_finish_find(&dir);
#endif

	if (s->sound_server) {
		if ((s->sound_server->save)(s, dirname)) {
			sciprintf("Saving failed for the sound subsystem\n");
			chdir ("..");
			return 1;
		}
	}

	fd = sci_create_file("heap");

	if (!IS_VALID_FD(fd) || write(fd, s->_heap->start, SCI_HEAP_SIZE) < SCI_HEAP_SIZE) {
		sciprintf("Saving failed for the heap state\n");
		close(fd);
		return 1;
	}

	close(fd);

	fh = fopen("state", "w" FO_TEXT);
	setvbuf(fh, NULL, _IOFBF, 262144);

	/* Calculate the time spent with this game */
	s->game_time = time(NULL) - s->game_start_time.tv_sec;

SCI_MEMTEST;
	%CFSMLWRITE state_t s INTO fh;
SCI_MEMTEST;

	fclose(fh);

	_gamestate_unfrob(s);


	chdir ("..");
	return 0;
}


state_t *
gamestate_restore(state_t *s, char *dirname)
{
	FILE *fh;
	int fd;
	int i;
	int read_eof = 0;
	state_t *retval;

	if (chdir (dirname)) {
		sciprintf("Game state '%s' does not exist\n", dirname);
		return NULL;
	}

	if (s->sound_server) {
		if ((s->sound_server->restore)(s, dirname)) {
			sciprintf("Restoring failed for the sound subsystem\n");
			return NULL;
		}
	}

	retval = (state_t *) sci_malloc(sizeof(state_t));
	retval->_heap = heap_new();
	retval->savegame_version = -1;
	_global_save_state = retval;
	retval->gfx_state = s->gfx_state;
	retval->old_screen = NULL;

	fh = fopen("state", "r" FO_TEXT);
	if (!fh) {
		heap_del(retval->_heap);
		free(retval);
		return NULL;
	}

	 /* Backwards compatibility settings */
	retval->amp_rest = 0;
	retval->dyn_views = NULL;
	retval->drop_views = NULL;
	retval->port = NULL;
	retval->save_dir_copy_buf = NULL;

	retval->sound_mute = s->sound_mute;
	retval->sound_volume = s->sound_volume;

	%CFSMLREAD-ATOMIC state_t retval FROM fh ERRVAR read_eof;

	fclose(fh);

	if ((retval->savegame_version < 1) || (retval->savegame_version > FREESCI_SAVEGAME_VERSION)) {

		if (retval->savegame_version < 3)
			sciprintf("Old savegame version detected- can't load\n");
		else
			sciprintf("Savegame version is %d- maximum supported is %0d\n", retval->savegame_version, FREESCI_SAVEGAME_VERSION);

		chdir("..");
		heap_del(retval->_heap);
		free(retval);
		return NULL;
	}

	if (read_eof || (!IS_VALID_FD(fd = open("heap", O_RDONLY | O_BINARY)))) {
		if (read_eof)
			sciprintf("Error while reading gamestate '%s'\n", dirname);
		else
			sciprintf("Heap file does not exist for game state '%s'\n", dirname);
		chdir("..");
		heap_del(retval->_heap);
		free(retval);
		return NULL;
	}

	read(fd, retval->_heap->start, SCI_HEAP_SIZE);
	close(fd);

	_gamestate_unfrob(retval);

	retval->wm_port = gfxw_find_port(retval->visual, 0);
	retval->titlebar_port = gfxw_find_port(retval->visual, 1);
	retval->picture_port = gfxw_find_port(retval->visual, 2);

	/* Set exec stack base to zero */
	retval->execution_stack_base = 0;

	/* Set the class table pointers to the script positions */
	for (i = 0; i < retval->classtable_size; i++)
		retval->classtable[i].scriptposp = &(retval->scripttable[retval->classtable[i].script].heappos);

	/* Now copy all current state information */
	/* Graphics and input state: */
	retval->animation_delay = s->animation_delay;
	retval->animation_granularity = s->animation_granularity;
	retval->gfx_state = s->gfx_state;
	gfxop_set_pointer_cursor(s->gfx_state, retval->mouse_pointer_nr);

	memcpy(retval->ega_colors, s->ega_colors, 16 * sizeof(gfx_color_t));

	if (FILE_VERSION > 1 && retval->pics_drawn_nr) {
		gfxop_new_pic(s->gfx_state, retval->pics[0].nr, 1, retval->pics[0].palette);
		for (i = 1; i < retval->pics_drawn_nr; i++)
			gfxop_add_to_pic(s->gfx_state, retval->pics[i].nr, 1, retval->pics[i].palette);
	} else {
		if (FILE_VERSION == 1) {
			retval->pics = sci_malloc(sizeof(drawn_pic_t) * (retval->pics_nr = 8));
			retval->pics_drawn_nr = 0;
		}
		gfxop_set_clip_zone(s->gfx_state, gfx_rect_fullscreen);
		gfxop_fill_box(s->gfx_state, gfx_rect_fullscreen, s->ega_colors[0]);
		gfxop_update(s->gfx_state);
	}
	gfxop_update_box(s->gfx_state, gfx_rect(0, 0, 320, 200));
	gfxop_clear_box(s->gfx_state, gfx_rect(0, 0, 320, 200));

	if (retval->visual) {
		gfxop_set_clip_zone(retval->gfx_state, gfx_rect_fullscreen);
		retval->visual->gfx_state = retval->gfx_state;
		retval->visual->add_dirty_abs(GFXWC(retval->visual), gfx_rect_fullscreen, 1);
		retval->visual->draw(GFXW(retval->visual), gfxw_point_zero);
	}
	gfxop_update_box(s->gfx_state, gfx_rect(0, 0, 320, 200));

	if (!retval->port && FILE_VERSION > 1) {
		fprintf(stderr,"Found no valid port for port serial number %08x!\n", retval->port_serial);
		retval->visual->print(GFXW(retval->visual), 0);
		return NULL;
	}

	/* Sound state: */
	retval->sound_server = s->sound_server;
/*
	memcpy(&(retval->sound_pipe_in[0]), &(s->sound_pipe_in[0]), sizeof(int[2]));
	memcpy(&(retval->sound_pipe_out[0]), &(s->sound_pipe_out[0]), sizeof(int[2]));
	memcpy(&(retval->sound_pipe_events[0]), &(s->sound_pipe_events[0]), sizeof(int[2]));
	memcpy(&(retval->sound_pipe_debug[0]), &(s->sound_pipe_debug[0]), sizeof(int[2]));
*/
	/* Time state: */
	sci_get_current_time(&(retval->last_wait_time));
	retval->game_start_time.tv_sec = time(NULL) - retval->game_time;
	retval->game_start_time.tv_usec = 0;

	/* File IO state: */
	retval->file_handles_nr = 2;
	retval->file_handles = sci_calloc(2, sizeof(FILE *));

	if (!retval->save_dir_copy_buf) { /* FIXME: Maybe other versions as well! */
		retval->save_dir_copy_buf = sci_malloc(MAX_SAVE_DIR_SIZE);
		retval->save_dir = heap_allocate(retval->_heap, MAX_SAVE_DIR_SIZE);
		if (!retval->save_dir) {
			fprintf(stderr, "ERROR: While restoring old savegame: Not enough space to allocate space for"
				" save_dir buffer!\n");
			return NULL;
			/* This should only happen with corrupt save states, since all games HAVE enough
			** spare memory for this.  */
		}
	}


	strcpy(retval->save_dir_copy_buf, s->save_dir_copy_buf);
	if (retval->save_dir)
		strcpy(retval->heap + retval->save_dir + 2, s->heap + s->save_dir + 2);

	/* static parser information: */
	retval->parser_rules = s->parser_rules;
	retval->parser_words_nr = s->parser_words_nr;
	retval->parser_words = s->parser_words;
	retval->parser_suffices_nr = s->parser_suffices_nr;
	retval->parser_suffices = s->parser_suffices;
	retval->parser_branches_nr = s->parser_branches_nr;
	retval->parser_branches = s->parser_branches;

	/* static VM/Kernel information: */
	retval->selector_names_nr = s->selector_names_nr;
	retval->selector_names = s->selector_names;
	retval->kernel_names_nr = s->kernel_names_nr;
	retval->kernel_names = s->kernel_names;
	retval->kfunct_table = s->kfunct_table;
	retval->opcodes = s->opcodes;

	memcpy(&(retval->selector_map), &(s->selector_map), sizeof(selector_map_t));

	/* Copy version information */
	retval->version = s->version;
	retval->max_version = s->max_version;
	retval->min_version = s->min_version;

	/* Copy breakpoint information from current game instance */
	retval->have_bp = s->have_bp;
	retval->bp_list = s->bp_list;

	retval->debug_mode = s->debug_mode;

	retval->resource_dir = s->resource_dir;
	retval->work_dir = s->work_dir;
	retval->kernel_opt_flags = 0;

	retval->resmgr = s->resmgr;

	if (retval->savegame_version < 5) {
		retval->status_bar_foreground = 0;
		retval->status_bar_background = 15;
	}

	if (retval->savegame_version < 3) {
		char *cwd = sci_getcwd();

		retval->save_dir = heap_allocate(retval->_heap, MAX_SAVE_DIR_SIZE);
		/* Compensate for save_dir location change */
		if (strlen(cwd) > MAX_SAVE_DIR_SIZE)
			sciprintf("Warning: cwd '%s' is longer than the"
				  " MAX_SAVE_DIR_SIZE %d\n",
				  cwd, MAX_SAVE_DIR_SIZE);
		else
			strcpy(retval->heap + retval->save_dir + 2, cwd);

		sci_free(cwd);

		retval->save_dir_copy = 0xffff;
		retval->save_dir_edit_offset = 0;
	}

	if (retval->savegame_version < 4) {
		char *cwd = sci_getcwd();
		retval->save_dir_copy_buf = sci_malloc(MAX_SAVE_DIR_SIZE);
		strcpy(retval->save_dir_copy_buf, cwd);
		sci_free(cwd);
	}

	retval->successor = NULL;
	retval->pic_priority_table = gfxop_get_pic_metainfo(retval->gfx_state);

	chdir ("..");

	return retval;
}
