/*
 * fastmem.c
 *
 * Memory related functions (fast version without virtual memory)
 *
 */

#if !defined( _WIN32_WCE ) || _WIN32_WCE >= 210
#include <stdio.h>
#else
#include "FrotzCEIO.h"
#endif

#include <string.h>

#ifndef _WIN32_WCE
#include "frotz.h"
#else
#include "Frotz\Frotz.h"
#endif

#ifdef __MSDOS__

#include <alloc.h>

#define malloc(size)	farmalloc (size)
#define realloc(size,p)	farrealloc (size,p)
#define free(size)	farfree (size)
#define memcpy(d,s,n)	_fmemcpy (d,s,n)

#else

#ifdef _WIN32_WCE

#include <windows.h>

#define malloc( size )		LocalAlloc( LPTR, (UINT) size )
#define realloc( size, p )	LocalReAlloc( (HLOCAL) p, (UINT) size, 0 )
#define free( p )			LocalFree( (HLOCAL) p )

#ifndef SEEK_SET
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif

#define far

#endif

#endif

extern void (*op0_opcodes[]) (void);
extern void (*op1_opcodes[]) (void);
extern void (*op2_opcodes[]) (void);
extern void (*var_opcodes[]) (void);

char save_name[MAX_FILE_NAME + 1] = DEFAULT_SAVE_NAME;
char auxilary_name[MAX_FILE_NAME + 1] = DEFAULT_AUXILARY_NAME;

zbyte far *zmp = NULL;
zbyte far *pcp = NULL;

FILE *story_fp = NULL;

zbyte far *undo[MAX_UNDO_SLOTS];

int undo_slots = 0;
int undo_count = 0;
int undo_valid = 0;

/*
 * restart_header
 *
 * Set all header fields which hold information about the interpreter.
 *
 */

void restart_header (void)
{
    zword screen_x_size;
    zword screen_y_size;
    zbyte font_x_size;
    zbyte font_y_size;

    int i;

    SET_BYTE (H_CONFIG, h_config)
    SET_WORD (H_FLAGS, h_flags)

    if (h_version >= V4) {
	SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number)
	SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version)
	SET_BYTE (H_SCREEN_ROWS, h_screen_rows)
	SET_BYTE (H_SCREEN_COLS, h_screen_cols)
    }

    /* It's less trouble to use font size 1x1 for V5 games, especially
       because of a bug in the unreleased German version of "Zork 1" */

    if (h_version != V6) {
	screen_x_size = ZWORD (h_screen_cols);
	screen_y_size = ZWORD (h_screen_rows);
	font_x_size = 1;
	font_y_size = 1;
    } else {
	screen_x_size = h_screen_width;
	screen_y_size = h_screen_height;
	font_x_size = h_font_width;
	font_y_size = h_font_height;
    }

    if (h_version >= V5) {
	SET_WORD (H_SCREEN_WIDTH, screen_x_size)
	SET_WORD (H_SCREEN_HEIGHT, screen_y_size)
	SET_BYTE (H_FONT_HEIGHT, font_y_size)
	SET_BYTE (H_FONT_WIDTH, font_x_size)
	SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background)
	SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground)
    }

    if (h_version == V6)
	for (i = 0; i < 8; i++)
	    storeb (ZWORD (H_USER_NAME + i), h_user_name[i]);

    SET_BYTE (H_STANDARD_HIGH, h_standard_high)
    SET_BYTE (H_STANDARD_LOW, h_standard_low)

}/* restart_header */

/*
 * init_memory
 *
 * Allocate memory and load the story file.
 *
 */

void init_memory (void)
{
    long size;
    zword addr;
    unsigned n;
    int i, j;

#ifndef AMIGA

    /* Amiga interface has its own game detection routines */

    static struct {
	zword release;
	zbyte serial[6];
	int id;
    } records[] = {
	{ 21, "871214", SHERLOCK },
	{ 26, "880127", SHERLOCK },
	{ 47, "870915", BEYOND_ZORK },
	{ 49, "870917", BEYOND_ZORK },
	{ 51, "870923", BEYOND_ZORK },
	{ 57, "871221", BEYOND_ZORK }
    };

#endif

    /* Open story file */

    if ((story_fp = fopen (story_name, "rb")) == NULL)
	os_fatal ("Cannot open story file");

    /* Allocate memory for story header */

    if ((zmp = (zbyte far *) malloc (64)) == NULL)
	os_fatal ("Out of memory");

    /* Load header into memory */

    if (fread (zmp, 1, 64, story_fp) != 64)
	os_fatal ("Story file read error");

    /* Copy header fields to global variables */

    LOW_BYTE (H_VERSION, h_version)

    if (h_version < V1 || h_version > V8)
	os_fatal ("Unknown Z-code version");

    LOW_BYTE (H_CONFIG, h_config)

    if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED))
	os_fatal ("Byte swapped story file");

    LOW_WORD (H_RELEASE, h_release)
    LOW_WORD (H_RESIDENT_SIZE, h_resident_size)
    LOW_WORD (H_START_PC, h_start_pc)
    LOW_WORD (H_DICTIONARY, h_dictionary)
    LOW_WORD (H_OBJECTS, h_objects)
    LOW_WORD (H_GLOBALS, h_globals)
    LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size)
    LOW_WORD (H_FLAGS, h_flags)

    for (i = 0, addr = H_SERIAL; i < 6; i++, addr++)
	LOW_BYTE (addr, h_serial[i])

#ifndef AMIGA

    /* Auto-detect buggy story files that need special fixes */

    for (i = 0; i < 6; i++) {

	if (h_release == records[i].release) {

	    for (j = 0; j < 6; j++)
		if (h_serial[j] != records[i].serial[j])
		    goto no_match;

	    story_id = records[i].id;

	}

    }

no_match:

#endif

    LOW_WORD (H_ABBREVIATIONS, h_abbreviations)
    LOW_WORD (H_FILE_SIZE, h_file_size)

    /* Calculate story file size in bytes */

    if (h_file_size) {

	story_size = (long) 2 * h_file_size;

	if (h_version >= V4)
	    story_size *= 2;
	if (h_version >= V6)
	    story_size *= 2;

    } else {		/* some old games lack the file size entry */

	fseek (story_fp, 0, SEEK_END);
	story_size = ftell (story_fp);
	fseek (story_fp, 64, SEEK_SET);

    }

    LOW_WORD (H_CHECKSUM, h_checksum)
    LOW_WORD (H_ALPHABET, h_alphabet)
    LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset)
    LOW_WORD (H_STRINGS_OFFSET, h_strings_offset)
    LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys)
    LOW_WORD (H_MOUSE_TABLE, h_mouse_table)

    /* Adjust opcode tables for pre-V5 games */

    if (h_version <= V4) 
	{
		op0_opcodes[0x09] = z_pop;
		op1_opcodes[0x0f] = z_not;
    }
 	else
 	{
 		op0_opcodes[0x09] = z_catch;
 		op1_opcodes[0x0f] = z_call_n;
 	}
    /* Allocate memory for story data */

	free( zmp );
	zmp = NULL;

    if ((zmp = (zbyte far *) malloc ( story_size)) == NULL)
		os_fatal ("Out of memory");

	fseek( story_fp, 0, SEEK_SET );
	
    if (fread (zmp, 1, 64, story_fp) != 64)
	os_fatal ("Story file read error");

    /* Load story file in chunks of 32KB */

    n = 0x8000;

    for (size = 64; size < story_size; size += n) {

	if (story_size - size < 0x8000)
	    n = (unsigned) (story_size - size);

	SET_PC (size)

	if (fread (pcp, 1, n, story_fp) != n)
	    os_fatal ("Story file read error");

    }

}/* init_memory */

/*
 * init_undo
 *
 * Allocate memory for multiple undo. It is important not to occupy
 * all the memory available, since the IO interface may need memory
 * during the game, e.g. for loading sounds or pictures.
 *
 */

void init_undo (void)
{
    void far *reserved;

	undo_slots = 0;
	undo_count = 0;
	undo_valid = 0;

    if (reserve_mem)
	if ((reserved = malloc (reserve_mem)) == NULL)
	    return;

    while (undo_slots < MAX_UNDO_SLOTS) {

	void far *mem = malloc ((long) sizeof (stack) + h_dynamic_size);

	if (mem == NULL)
	    break;

	undo[undo_slots++] = mem;

    }

    if (reserve_mem)
	free (reserved);

}/* init_undo */

/*
 * reset_memory
 *
 * Close the story file and deallocate memory.
 *
 */

void reset_memory (void)
{
int nSlot;

    fclose (story_fp);

	for (nSlot = 0; nSlot < MAX_UNDO_SLOTS; nSlot++)
		free (undo[nSlot]);

	undo_slots = 0;
	undo_count = 0;
	undo_valid = 0;

    free (zmp);

}/* reset_memory */

/*
 * storeb
 *
 * Write a byte value to the dynamic Z-machine memory.
 *
 */

void storeb (zword addr, zbyte value)
{

    if (addr >= h_dynamic_size)
	runtime_error ("Store out of dynamic memory");

    if (addr == H_FLAGS + 1) {	/* flags register is modified */

	h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG);
	h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG);

	if (value & SCRIPTING_FLAG) {
	    if (!ostream_script)
		script_open ();
	} else {
	    if (ostream_script)
		script_close ();
	}

	refresh_text_style ();

    }

    SET_BYTE (addr, value)

}/* storeb */

/*
 * storew
 *
 * Write a word value to the dynamic Z-machine memory.
 *
 */

void storew (zword addr, zword value)
{

    storeb (ZWORD (addr + 0), (zbyte) hi (value));
    storeb (ZWORD (addr + 1), (zbyte) lo (value));

}/* storew */

/*
 * z_restart, re-load dynamic area, clear the stack and set the PC.
 *
 * 	no zargs used
 *
 */

void z_restart (void)
{
    static first_restart = 1;

    restart_screen ();

    seed_random (0);

    if (!first_restart) {

	fseek (story_fp, 0, SEEK_SET);

	if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size)
	    os_fatal ("Story file read error");

    } else first_restart = 0;

    restart_header ();

    sp = fp = stack + STACK_SIZE;

    if (h_version != V6) {

	long pc = (long) h_start_pc;
	SET_PC (pc)

    } else call (h_start_pc, 0, NULL, 0);

}/* z_restart */

/*
 * get_default_name
 *
 * Read a default file name from the memory of the Z-machine and
 * copy it to a string.
 *
 */

static void get_default_name (char *default_name, zword addr)
{

    if (addr) {

	zbyte len;
	int i;

	LOW_BYTE (addr, len)
	addr++;

	for (i = 0; i < len; i++) {

	    zbyte c;

	    LOW_BYTE (addr, c)
	    addr++;

	    default_name[i] = c;

	}

	default_name[i] = 0;

	if (strchr (default_name, '.') == NULL)
	    strcpy (default_name + i, ".AUX");

    } else strcpy (default_name, auxilary_name);

}/* get_default_name */

/*
 * z_restore, restore [a part of] a Z-machine state from disk
 *
 *	zargs[0] = address of area to restore (optional)
 *	zargs[1] = number of bytes to restore
 *	zargs[2] = address of suggested file name
 *
 */

void z_restore (void)
{
    char new_name[MAX_FILE_NAME + 1];
    char default_name[MAX_FILE_NAME + 1];
    FILE *gfp;

    int success = 0;

    if (zargc != 0) {

	/* Get the file name */

	get_default_name (default_name, ZWORD ((zargc >= 3) ? zargs[2] : 0));

	if (os_read_file_name (new_name, default_name, FILE_LOAD_AUX) == 0)
	    goto finished;

	strcpy (auxilary_name, default_name);

	/* Open auxilary file */

	if ((gfp = fopen (new_name, "rb")) == NULL)
	    goto finished;

	/* Load auxilary file */

	success = fread (zmp + zargs[1], 1, zargs[2], gfp);

	/* Close auxilary file */

	fclose (gfp);

    } else {

	long pc;
	zword release;
	zword addr;
	int i;

	/* Get the file name */

	if (os_read_file_name (new_name, save_name, FILE_RESTORE) == 0)
	    goto finished;

	strcpy (save_name, new_name);

	/* Open game file */

	if ((gfp = fopen (new_name, "rb")) == NULL)
	    goto finished;

	/* Load game file */

	release = (unsigned) fgetc (gfp) << 8;
	release |= fgetc (gfp);

	(void) fgetc (gfp);
	(void) fgetc (gfp);

	/* Check the release number */

	if (release == h_release) {

	    pc = (long) fgetc (gfp) << 16;
	    pc |= (unsigned) fgetc (gfp) << 8;
	    pc |= fgetc (gfp);

	    SET_PC (pc)

	    sp = stack + (fgetc (gfp) << 8);
	    sp += fgetc (gfp);
	    fp = stack + (fgetc (gfp) << 8);
	    fp += fgetc (gfp);

	    for (i = (int) (sp - stack); i < STACK_SIZE; i++) {
		stack[i] = (unsigned) fgetc (gfp) << 8;
		stack[i] |= fgetc (gfp);
	    }

	    fseek (story_fp, 0, SEEK_SET);

	    for (addr = 0; addr < h_dynamic_size; addr++) {
		int skip = fgetc (gfp);
		for (i = 0; i < skip; i++)
		    zmp[addr++] = ZBYTE (fgetc (story_fp));
		zmp[addr] = ZBYTE (fgetc (gfp));
		(void) fgetc (story_fp);
	    }

	    /* Check for errors */

	    if (ferror (gfp) || ferror (story_fp))
		os_fatal ("Error reading save file");

	    /* Reset upper window (V3 only) */

	    if (h_version == V3)
		split_window (0);

	    /* Initialise story header */

	    restart_header ();

	    /* Success */

	    success = 2;

	} else print_string ("Invalid save file\n");

	/* Close game file */

	fclose (gfp);

    }

finished:

    if (h_version <= V3)
	branch (success);
    else
	store (ZWORD (success));

}/* z_restore */

/*
 * restore_undo
 *
 * This function does the dirty work for z_restore_undo.
 *
 */

int restore_undo (void)
{

    if (undo_slots == 0)	/* undo feature unavailable */

	return -1;

    else if (undo_valid == 0)	/* no saved game state */

	return 0;

    else {			/* undo possible */

	long pc;

	if (undo_count == 0)
	    undo_count = undo_slots;

	memcpy (stack, undo[undo_count - 1], sizeof (stack));
	memcpy (zmp, undo[undo_count - 1] + sizeof (stack), h_dynamic_size);

	pc = ((long) stack[0] << 16) | stack[1];
	sp = stack + stack[2];
	fp = stack + stack[3];

	SET_PC (pc)

	restart_header ();

	undo_count--;
	undo_valid--;

	return 2;

    }

}/* restore_undo */

/*
 * z_restore_undo, restore a Z-machine state from memory.
 *
 *	no zargs used
 *
 */

void z_restore_undo (void)
{

    store (ZWORD (restore_undo ()));

}/* restore_undo */

/*
 * z_save, save [a part of] the Z-machine state to disk.
 *
 *	zargs[0] = address of memory area to save (optional)
 *	zargs[1] = number of bytes to save
 *	zargs[2] = address of suggested file name
 *
 */

void z_save (void)
{
    char new_name[MAX_FILE_NAME + 1];
    char default_name[MAX_FILE_NAME + 1];
    FILE *gfp;

    int success = 0;

    if (zargc != 0) {

	/* Get the file name */

	get_default_name (default_name, ZWORD ((zargc >= 3) ? zargs[2] : 0));

	if (os_read_file_name (new_name, default_name, FILE_SAVE_AUX) == 0)
	    goto finished;

	strcpy (auxilary_name, default_name);

	/* Open auxilary file */

	if ((gfp = fopen (new_name, "wb")) == NULL)
	    goto finished;

	/* Write auxilary file */

	success = fwrite (zmp + zargs[0], zargs[1], 1, gfp);

	/* Close auxilary file */

	fclose (gfp);

    } else {

	long pc;
	zword addr;
	int nsp, nfp;
	int skip;
	int i;

	/* Get the file name */

	if (os_read_file_name (new_name, save_name, FILE_SAVE) == 0)
	    goto finished;

	strcpy (save_name, new_name);

	/* Open game file */

	if ((gfp = fopen (new_name, "wb")) == NULL)
	    goto finished;

	/* Write game file */

	fputc ((int) hi (h_release), gfp);
	fputc ((int) lo (h_release), gfp);
	fputc ((int) hi (h_checksum), gfp);
	fputc ((int) lo (h_checksum), gfp);

	GET_PC (pc)

	fputc ((int) (pc >> 16) & 0xff, gfp);
	fputc ((int) (pc >> 8) & 0xff, gfp);
	fputc ((int) (pc) & 0xff, gfp);

	nsp = (int) (sp - stack);
	nfp = (int) (fp - stack);

	fputc ((int) hi (nsp), gfp);
	fputc ((int) lo (nsp), gfp);
	fputc ((int) hi (nfp), gfp);
	fputc ((int) lo (nfp), gfp);

	for (i = nsp; i < STACK_SIZE; i++) {
	    fputc ((int) hi (stack[i]), gfp);
	    fputc ((int) lo (stack[i]), gfp);
	}

	fseek (story_fp, 0, SEEK_SET);

	for (addr = 0, skip = 0; addr < h_dynamic_size; addr++)
	    if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) {
		fputc (skip, gfp);
		fputc (zmp[addr], gfp);
		skip = 0;
	    } else skip++;

	/* Close game file and check for errors */

	if (fclose (gfp) == EOF || ferror (story_fp)) {
	    print_string ("Error writing save file\n");
	    goto finished;
	}

	/* Success */

	success = 1;

    }

finished:

    if (h_version <= V3)
	branch (success);
    else
	store (ZWORD (success));

}/* z_save */

/*
 * save_undo
 *
 * This function does the dirty work for z_save_undo.
 *
 */

int save_undo (void)
{
    long pc;

    if (undo_slots == 0)	/* undo feature unavailable */

	return -1;

    else {			/* save undo possible */

	if (undo_count == undo_slots)
	    undo_count = 0;

	GET_PC (pc)

	stack[0] = ZWORD (pc >> 16);
	stack[1] = ZWORD (pc & 0xffff);
	stack[2] = ZWORD (sp - stack);
	stack[3] = ZWORD (fp - stack);

	memcpy (undo[undo_count], stack, sizeof (stack));
	memcpy (undo[undo_count] + sizeof (stack), zmp, h_dynamic_size);

	if (++undo_count == undo_slots)
	    undo_count = 0;
	if (++undo_valid > undo_slots)
	    undo_valid = undo_slots;

	return 1;

    }

}/* save_undo */

/*
 * z_save_undo, save the current Z-machine state for a future undo.
 *
 *	no zargs used
 *
 */

void z_save_undo (void)
{

    store (ZWORD (save_undo ()));

}/* z_save_undo */

/*
 * z_verify, verify the story file integrity.
 *
 *	no zargs used
 *
 */

void z_verify (void)
{
    zword checksum = 0;
    long i;

    /* Sum all bytes in story file except header bytes */

    fseek (story_fp, 64, SEEK_SET);

    for (i = 64; i < story_size; i++)
	checksum += fgetc (story_fp);

    /* Branch if the checksums are equal */

    branch (checksum == h_checksum);

}/* z_verify */
