/******************************************************************************
 *  Microtan 65
 *
 *  machine driver
 *
 *  Juergen Buchmueller <pullmoll@t-online.de>, Jul 2000
 *
 *  Thanks go to Geoff Macdonald <mail@geoff.org.uk>
 *  for his site http:://www.geo255.redhotant.com
 *  and to Fabrice Frances <frances@ensica.fr>
 *  for his site http://www.ifrance.com/oric/microtan.html
 *
 *****************************************************************************/

/* Core includes */
#include "driver.h"
#include "includes/microtan.h"

/* Components */
#include "cpu/m6502/m6502.h"
#include "machine/6522via.h"
#include "machine/6551.h"
#include "sound/ay8910.h"

/* Devices */
#include "devices/cassette.h"
#include "devices/snapquik.h"


#ifndef VERBOSE
#define VERBOSE 0
#endif

#define LOG(x)  do { if (VERBOSE) logerror x; } while (0)

static UINT8 microtan_keypad_column;
static UINT8 microtan_keyboard_ascii;

static emu_timer *microtan_timer = NULL;

static int via_0_irq_line = CLEAR_LINE;
static int via_1_irq_line = CLEAR_LINE;
static int kbd_irq_line = CLEAR_LINE;

static UINT8 keyrows[10] = { 0,0,0,0,0,0,0,0,0,0 };
static const char keyboard[8][9][8] = {
    { /* normal */
        { 27,'1','2','3','4','5','6','7'},
        {'8','9','0','-','=','`',127,  9},
        {'q','w','e','r','t','y','u','i'},
        {'o','p','[',']', 13,127,  0,  0},
        {'a','s','d','f','g','h','j','k'},
        {'l',';', 39, 92,  0,'z','x','c'},
        {'v','b','n','m',',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Shift */
        { 27,'!','@','#','$','%','^','&'},
        {'*','(',')','_','+','~',127,  9},
        {'Q','W','E','R','T','Y','U','I'},
        {'O','P','{','}', 13,127,  0,  0},
        {'A','S','D','F','G','H','J','K'},
        {'L',':','"','|',  0,'Z','X','C'},
        {'V','B','N','M','<','>','?',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Control */
        { 27,'1','2','3','4','5','6','7'},
        {'8','9','0','-','=','`',127,  9},
        { 17, 23,  5, 18, 20, 25, 21,  9},
        { 15, 16, 27, 29, 13,127,  0,  0},
        {  1, 19,  4,  6,  7,  8, 10, 11},
        { 12,';', 39, 28,  0, 26, 24,  3},
        { 22,  2, 14, 13,',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Shift+Control */
        { 27,'!',  0,'#','$','%', 30,'&'},
        {'*','(',')', 31,'+','~',127,  9},
        { 17, 23,  5, 18, 20, 25, 21,  9},
        { 15, 16, 27, 29, 13,127,  0,  0},
        {  1, 19,  4,  6,  7,  8, 10, 11},
        { 12,':','"', 28,  0, 26, 24,  3},
        { 22,  2, 14, 13,',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* CapsLock */
        { 27,'1','2','3','4','5','6','7'},
        {'8','9','0','-','=','`',127,  9},
        {'Q','W','E','R','T','Y','U','I'},
        {'O','P','[',']', 13,127,  0,  0},
        {'A','S','D','F','G','H','J','K'},
        {'L',';', 39, 92,  0,'Z','X','C'},
        {'V','B','N','M',',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Shift+CapsLock */
        { 27,'!','@','#','$','%','^','&'},
        {'*','(',')','_','+','~',127,  9},
        {'Q','W','E','R','T','Y','U','I'},
        {'O','P','{','}', 13,127,  0,  0},
        {'A','S','D','F','G','H','J','K'},
        {'L',':','"','|',  0,'Z','X','C'},
        {'V','B','N','M','<','>','?',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Control+CapsLock */
        { 27,'1','2','3','4','5','6','7'},
        {'8','9','0','-','=','`',127,  9},
        { 17, 23,  5, 18, 20, 25, 21,  9},
        { 15, 16, 27, 29, 13,127,  0,  0},
        {  1, 19,  4,  6,  7,  8, 10, 11},
        { 12,';', 39, 28,  0, 26, 24,  3},
        { 22,  2, 14, 13,',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
    { /* Shift+Control+CapsLock */
        { 27,'!',  0,'#','$','%', 30,'&'},
        {'*','(',')', 31,'+','~',127,  9},
        { 17, 23,  5, 18, 20, 25, 21,  9},
        { 15, 16, 27, 29, 13,127,  0,  0},
        {  1, 19,  4,  6,  7,  8, 10, 11},
        { 12,':','"', 28,  0, 26, 24,  3},
        { 22,  2, 14, 13,',','.','/',  0},
        { 10,' ','-',',', 13,'.','0','1'},
        {'2','3','4','5','6','7','8','9'},
    },
};

static UINT8 read_dsw(running_machine *machine)
{
	UINT8 result;
	switch(mame_get_phase(machine))
	{
		case MAME_PHASE_RESET:
		case MAME_PHASE_RUNNING:
			result = input_port_read(machine, "DSW");
			break;

		default:
			result = 0x00;
			break;
	}
	return result;
}

static void microtan_set_irq_line(running_machine *machine)
{
    /* The 6502 IRQ line is active low and probably driven
       by open collector outputs (guess). Since MAME/MESS use
       a non-0 value for ASSERT_LINE we OR the signals here */
    cpu_set_input_line(machine->cpu[0], 0, via_0_irq_line | via_1_irq_line | kbd_irq_line);
}

static const device_config *cassette_device_image(running_machine *machine)
{
	return devtag_get_device(machine, "cassette");
}

/**************************************************************
 * VIA callback functions for VIA #0
 **************************************************************/
static READ8_DEVICE_HANDLER (via_0_in_a )
{
    int data = input_port_read(device->machine, "JOY");
    LOG(("microtan_via_0_in_a %02X\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_0_in_b )
{
    int data = 0xff;
    LOG(("microtan_via_0_in_b %02X\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_0_in_ca1 )
{
    int data = 1;
    LOG(("microtan_via_0_in_ca1 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_0_in_cb1 )
{
    int data = 1;
    LOG(("microtan_via_0_in_cb1 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_0_in_ca2 )
{
    int data = 1;
    LOG(("microtan_via_0_in_ca2 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_0_in_cb2 )
{
    int data = 1;
    LOG(("microtan_via_0_in_cb2 %d\n", data));
    return data;
}

static WRITE8_DEVICE_HANDLER ( via_0_out_a )
{
    LOG(("microtan_via_0_out_a %02X\n", data));
}

static WRITE8_DEVICE_HANDLER (via_0_out_b )
{
    LOG(("microtan_via_0_out_b %02X\n", data));
    /* bit #7 is the cassette output signal */
    cassette_output(cassette_device_image(device->machine), data & 0x80 ? +1.0 : -1.0);
}

static WRITE8_DEVICE_HANDLER ( via_0_out_ca2 )
{
    LOG(("microtan_via_0_out_ca2 %d\n", data));
}

static WRITE8_DEVICE_HANDLER (via_0_out_cb2 )
{
    LOG(("microtan_via_0_out_cb2 %d\n", data));
}

static void via_0_irq(const device_config *device, int state)
{
    LOG(("microtan_via_0_irq %d\n", state));
    via_0_irq_line = state;
    microtan_set_irq_line(device->machine);
}

/**************************************************************
 * VIA callback functions for VIA #1
 **************************************************************/
static READ8_DEVICE_HANDLER ( via_1_in_a )
{
    int data = 0xff;
    LOG(("microtan_via_1_in_a %02X\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_1_in_b )
{
    int data = 0xff;
    LOG(("microtan_via_1_in_b %02X\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_1_in_ca1 )
{
    int data = 1;
    LOG(("microtan_via_1_in_ca1 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_1_in_cb1 )
{
    int data = 1;
    LOG(("microtan_via_1_in_cb1 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_1_in_ca2 )
{
    int data = 1;
    LOG(("microtan_via_1_in_ca2 %d\n", data));
    return data;
}

static READ8_DEVICE_HANDLER ( via_1_in_cb2 )
{
    int data = 1;
    LOG(("microtan_via_1_in_cb2 %d\n", data));
    return data;
}

static WRITE8_DEVICE_HANDLER ( via_1_out_a )
{
    LOG(("microtan_via_1_out_a %02X\n", data));
}

static WRITE8_DEVICE_HANDLER ( via_1_out_b )
{
    LOG(("microtan_via_1_out_b %02X\n", data));
}

static WRITE8_DEVICE_HANDLER (via_1_out_ca2 )
{
    LOG(("microtan_via_1_out_ca2 %d\n", data));
}

static WRITE8_DEVICE_HANDLER ( via_1_out_cb2 )
{
    LOG(("microtan_via_1_out_cb2 %d\n", data));
}

static void via_1_irq(const device_config *device, int state)
{
    LOG(("microtan_via_1_irq %d\n", state));
    via_1_irq_line = state;
    microtan_set_irq_line(device->machine);
}

/**************************************************************
 * VIA interface structure
 **************************************************************/
const via6522_interface microtan_via6522_0 =
{
	/* VIA#1 at bfc0-bfcf*/
	DEVCB_HANDLER(via_0_in_a),   DEVCB_HANDLER(via_0_in_b),
	DEVCB_HANDLER(via_0_in_ca1), DEVCB_HANDLER(via_0_in_cb1),
	DEVCB_HANDLER(via_0_in_ca2), DEVCB_HANDLER(via_0_in_cb2),
	DEVCB_HANDLER(via_0_out_a),  DEVCB_HANDLER(via_0_out_b),
	DEVCB_NULL, DEVCB_NULL,
	DEVCB_HANDLER(via_0_out_ca2),DEVCB_HANDLER(via_0_out_cb2),
	DEVCB_LINE(via_0_irq)
};

const via6522_interface microtan_via6522_1 =
{
	/* VIA#1 at bfe0-bfef*/
	DEVCB_HANDLER(via_1_in_a),   DEVCB_HANDLER(via_1_in_b),
	DEVCB_HANDLER(via_1_in_ca1), DEVCB_HANDLER(via_1_in_cb1),
	DEVCB_HANDLER(via_1_in_ca2), DEVCB_HANDLER(via_1_in_cb2),
	DEVCB_HANDLER(via_1_out_a),  DEVCB_HANDLER(via_1_out_b),
	DEVCB_NULL, DEVCB_NULL,
	DEVCB_HANDLER(via_1_out_ca2),DEVCB_HANDLER(via_1_out_cb2),
	DEVCB_LINE(via_1_irq)
};

static TIMER_CALLBACK(microtan_read_cassette)
{
	double level = cassette_input(cassette_device_image(machine));
	const device_config *via_0 = devtag_get_device(machine, "via6522_0");

	LOG(("microtan_read_cassette: %g\n", level));
	if (level < -0.07)
		via_cb2_w(via_0, 0, 0);
	else if (level > +0.07)
		via_cb2_w(via_0, 0, 1);
}

READ8_HANDLER( microtan_sound_r )
{
    int data = 0xff;
    LOG(("microtan_sound_r: -> %02x\n", data));
    return data;
}

WRITE8_HANDLER( microtan_sound_w )
{
    LOG(("microtan_sound_w: <- %02x\n", data));
}


 READ8_HANDLER ( microtan_bffx_r )
{
    int data = 0xff;
    switch( offset & 3 )
    {
    case  0: /* BFF0: read enables chunky graphics */
        microtan_chunky_graphics = 1;
        LOG(("microtan_bff0_r: -> %02x (chunky graphics on)\n", data));
        break;
    case  1: /* BFF1: read undefined (?) */
        LOG(("microtan_bff1_r: -> %02x\n", data));
        break;
    case  2: /* BFF2: read undefined (?) */
        LOG(("microtan_bff2_r: -> %02x\n", data));
        break;
    default: /* BFF3: read keyboard ASCII value */
        data = microtan_keyboard_ascii;
        LOG(("microtan_bff3_r: -> %02x (keyboard ASCII)\n", data));
    }
    return data;
}


/* This callback is called one clock cycle after BFF2 is written (delayed nmi) */
static TIMER_CALLBACK(microtan_pulse_nmi)
{
    cpu_set_input_line(machine->cpu[0], INPUT_LINE_NMI, PULSE_LINE);
}

WRITE8_HANDLER ( microtan_bffx_w )
{
    switch( offset & 3 )
    {
    case 0: /* BFF0: write reset keyboard interrupt flag */
        /* This removes bit 7 from the ASCII value of the last key pressed. */
        LOG(("microtan_bff0_w: %d <- %02x (keyboard IRQ clear )\n", offset, data));
        microtan_keyboard_ascii &= ~0x80;
        kbd_irq_line = CLEAR_LINE;
        microtan_set_irq_line(space->machine);
        break;
    case 1: /* BFF1: write delayed NMI */
        LOG(("microtan_bff1_w: %d <- %02x (delayed NMI)\n", offset, data));
        timer_set(space->machine, cpu_clocks_to_attotime(space->machine->cpu[0], 8), NULL, 0, microtan_pulse_nmi);
        break;
    case 2: /* BFF2: write keypad column write (what is this meant for?) */
        LOG(("microtan_bff2_w: %d <- %02x (keypad column)\n", offset, data));
        microtan_keypad_column = data;
        break;
    default: /* BFF3: write disable chunky graphics */
        LOG(("microtan_bff3_w: %d <- %02x (chunky graphics off)\n", offset, data));
        microtan_chunky_graphics = 0;
    }
}

static int microtan_varify_snapshot(UINT8 *data, int size)
{
	if (size == 8263)
	{
		LOG(("microtan_snapshot_id: magic size %d found\n", size));
		return IMAGE_VERIFY_PASS;
	}
	else
	{
		if (4 + data[2] + 256 * data[3] + 1 + 16 + 16 + 16 + 1 + 1 + 16 + 16 + 64 + 7 == size)
		{
			LOG(("microtan_snapshot_id: header RAM size + structures matches filesize %d\n", size));
			return IMAGE_VERIFY_PASS;
		}
	}

    return IMAGE_VERIFY_FAIL;
}

static int parse_intel_hex(UINT8 *snapshot_buff, char *src)
{
    char line[128];
    int row = 0, column = 0, last_addr = 0, last_size = 0;

    while (*src)
    {
        if (*src == '\r' || *src == '\n')
        {
            if (column)
            {
                unsigned int size, addr, null, b[32], cs, n;

                line[column] = '\0';
                row++;
                n = sscanf(line, ":%02x%04x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                    &size, &addr, &null,
                    &b[ 0], &b[ 1], &b[ 2], &b[ 3], &b[ 4], &b[ 5], &b[ 6], &b[ 7],
                    &b[ 8], &b[ 9], &b[10], &b[11], &b[12], &b[13], &b[14], &b[15],
                    &b[16], &b[17], &b[18], &b[19], &b[20], &b[21], &b[22], &b[23],
                    &b[24], &b[25], &b[26], &b[27], &b[28], &b[29], &b[30], &b[31],
                    &cs);
                if (n == 0)
                {
                    LOG(("parse_intel_hex: malformed line [%s]\n", line));
                }
                else if (n == 1)
                {
                    LOG(("parse_intel_hex: only size found [%s]\n", line));
                }
                else if (n == 2)
                {
                    LOG(("parse_intel_hex: only size and addr found [%s]\n", line));
                }
                else if (n == 3)
                {
                    LOG(("parse_intel_hex: only size, addr and null found [%s]\n", line));
                }
                else
                if (null != 0)
                {
                    LOG(("parse_intel_hex: warning null byte is != 0 [%s]\n", line));
                }
                else
                {
                    int i, sum;

                    n -= 3;

                    sum = size + (addr & 0xff) + ((addr >> 8) & 0xff);
                    if (n != 32 + 1)
                        cs = b[n-1];

                    last_addr = addr;
                    last_size = n-1;
                    LOG(("parse_intel_hex: %04X", addr));
                    for (i = 0; i < n-1; i++)
                    {
                        sum += b[i];
                        snapshot_buff[addr++] = b[i];
                    }
                    LOG(("-%04X checksum %02X+%02X = %02X\n", addr-1, cs, sum & 0xff, (cs + sum) & 0xff));
                }
            }
            column = 0;
        }
        else
        {
            line[column++] = *src;
        }
        src++;
    }
    /* register preset? */
    if (last_size == 7)
    {
        LOG(("parse_intel_hex: registers (?) at %04X\n", last_addr));
        memcpy(&snapshot_buff[8192+64], &snapshot_buff[last_addr], last_size);
    }
    return INIT_PASS;
}

static int parse_zillion_hex(UINT8 *snapshot_buff, char *src)
{
    char line[128];
    int parsing = 0, row = 0, column = 0;

    while (*src)
    {
        if (parsing)
        {
            if (*src == '}')
                parsing = 0;
            else
            {
                if (*src == '\r' || *src == '\n')
                {
                    if (column)
                    {
                        unsigned int addr, b[8], n;

                        line[column] = '\0';
                        row++;
                        n = sscanf(line, "%x %x %x %x %x %x %x %x %x", &addr, &b[0], &b[1], &b[2], &b[3], &b[4], &b[5], &b[6], &b[7]);
                        if (n == 0)
                        {
                            LOG(("parse_zillion_hex: malformed line [%s]\n", line));
                        }
                        else if (n == 1)
                        {
                            LOG(("parse_zillion_hex: only addr found [%s]\n", line));
                        }
                        else
                        {
                            int i;

                            LOG(("parse_zillion_hex: %04X", addr));
                            for (i = 0; i < n-1; i++)
                                snapshot_buff[addr++] = b[i];
                            LOG(("-%04X\n", addr-1));
                        }
                    }
                    column = 0;
                }
                else
                {
                    line[column++] = *src;
                }
            }
        }
        else
        {
            if (*src == '\r' || *src == '\n')
            {
                if (column)
                {
                    int addr, n;

                    row++;
                    line[column] = '\0';
                    n = sscanf(line, "G%x", (unsigned int *) &addr);
                    if (n == 1 && !snapshot_buff[8192+64+1] && !snapshot_buff[8192+64+1])
                    {
                        LOG(("microtan_hexfile_init: go addr %04X\n", addr));
                        snapshot_buff[8192+64+0] = addr & 0xff;
                        snapshot_buff[8192+64+1] = (addr >> 8) & 0xff;
                    }
                }
                column = 0;
            }
            else
            {
                line[column++] = *src;
            }
            if (*src == '{')
            {
                parsing = 1;
                column = 0;
            }
        }
        src++;
    }
    return INIT_PASS;
}

static void store_key(running_machine *machine, int key)
{
    LOG(("microtan: store key '%c'\n", key));
    microtan_keyboard_ascii = key | 0x80;
    kbd_irq_line = ASSERT_LINE;
    microtan_set_irq_line(machine);
}

INTERRUPT_GEN( microtan_interrupt )
{
    int mod, row, col, chg, new;
    static int lastrow = 0, mask = 0x00, key = 0x00, repeat = 0, repeater = 0;
	static const char *const keynames[] = { "ROW0", "ROW1", "ROW2", "ROW3", "ROW4", "ROW5", "ROW6", "ROW7", "ROW8" };

    if( repeat )
    {
        if( !--repeat )
            repeater = 4;
    }
    else if( repeater )
    {
        repeat = repeater;
    }


    row = 9;
    new = input_port_read(device->machine, "ROW8");
    chg = keyrows[--row] ^ new;

	while ( !chg && row > 0)
	{
		new = input_port_read(device->machine, keynames[row - 1]); 
		chg = keyrows[--row] ^ new; 
	}
    if (!chg) 
		--row;

    if (row >= 0)
    {
        repeater = 0x00;
        mask = 0x00;
        key = 0x00;
        lastrow = row;
        /* CapsLock LED */
		if( row == 3 && chg == 0x80 )
			set_led_status(1, (keyrows[3] & 0x80) ? 0 : 1);
		
        if (new & chg)  /* key(s) pressed ? */
        {
            mod = 0;

            /* Shift modifier */
            if ( (keyrows[5] & 0x10) || (keyrows[6] & 0x80) )
                mod |= 1;

            /* Control modifier */
            if (keyrows[3] & 0x40)
                mod |= 2;

            /* CapsLock modifier */
            if (keyrows[3] & 0x80)
                mod |= 4;

            /* find new key */
            mask = 0x01;
            for (col = 0; col < 8; col ++)
            {
                if (chg & mask)
                {
                    new &= mask;
                    key = keyboard[mod][row][col];
                    break;
                }
                mask <<= 1;
            }
            if( key )   /* normal key */
            {
                repeater = 30;
                store_key(device->machine, key);
            }
            else
            if( (row == 0) && (chg == 0x04) ) /* Ctrl-@ (NUL) */
                store_key(device->machine, 0);
            keyrows[row] |= new;
        }
        else
        {
            keyrows[row] = new;
        }
        repeat = repeater;
    }
    else
    if ( key && (keyrows[lastrow] & mask) && repeat == 0 )
    {
        store_key(device->machine, key);
    }
}

static void microtan_set_cpu_regs(running_machine *machine,const UINT8 *snapshot_buff, int base)
{
    LOG(("microtan_snapshot_copy: PC:%02X%02X P:%02X A:%02X X:%02X Y:%02X SP:1%02X",
        snapshot_buff[base+1], snapshot_buff[base+0], snapshot_buff[base+2], snapshot_buff[base+3],
        snapshot_buff[base+4], snapshot_buff[base+5], snapshot_buff[base+6]);
    cpu_set_reg(machine->cpu[0], M6502_PC, snapshot_buff[base+0] + 256 * snapshot_buff[base+1]));
    cpu_set_reg(machine->cpu[0], M6502_P, snapshot_buff[base+2]);
    cpu_set_reg(machine->cpu[0], M6502_A, snapshot_buff[base+3]);
    cpu_set_reg(machine->cpu[0], M6502_X, snapshot_buff[base+4]);
    cpu_set_reg(machine->cpu[0], M6502_Y, snapshot_buff[base+5]);
    cpu_set_reg(machine->cpu[0], M6502_S, snapshot_buff[base+6]);
}

static void microtan_snapshot_copy(running_machine *machine, UINT8 *snapshot_buff, int snapshot_size)
{
    UINT8 *RAM = memory_region(machine, "maincpu");
	const address_space *space = cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM);
	const device_config *via_0 = devtag_get_device(machine, "via6522_0");
	const device_config *via_1 = devtag_get_device(machine, "via6522_1");
	const device_config *ay8910 = devtag_get_device(machine, "ay8910.1");
	
    /* check for .DMP file format */
    if (snapshot_size == 8263)
    {
        int i, base;
        /********** DMP format
         * Lower 8k of RAM (0000 to 1fff)
         * 64 bytes of chunky graphics bits (first byte bit is for character at 0200, bit 1=0201, etc)
         * 7 bytes of CPU registers (PCL, PCH, PSW, A, IX, IY, SP)
         */
        LOG(("microtan_snapshot_copy: magic size %d found, assuming *.DMP format\n", snapshot_size));

        base = 0;
        /* 8K of RAM from 0000 to 1fff */
        memcpy(RAM, &snapshot_buff[base], 8192);
        base += 8192;
        /* 64 bytes of chunky graphics info */
        for (i = 0; i < 32*16; i++)
        {
            microtan_chunky_buffer[i] = (snapshot_buff[base+i/8] >> (i&7)) & 1;
        }
        base += 64;
        microtan_set_cpu_regs(machine, snapshot_buff, base);
    }
    else
    {
        int i, ramend, base;
        /********** M65 format ************************************
         *  2 bytes: File version
         *  2 bytes: RAM size
         *  n bytes: RAM (0000 to RAM Size)
         * 16 bytes: 1st 6522 (0xbfc0 to 0xbfcf)
         * 16 bytes: 2ns 6522 (0xbfe0 to 0xbfef)
         * 16 bytes: Microtan IO (0xbff0 to 0xbfff)
         *  1 byte : Invaders sound (0xbc04)
         *  1 byte : Chunky graphics state (0=off, 1=on)
         * 16 bytes: 1st AY8910 registers
         * 16 bytes: 2nd AY8910 registers
         * 64 bytes: Chunky graphics bits (first byte bit 0 is for character at 0200, bit 1=0201, etc)
         *  7 bytes: CPU registers (PCL, PCH, PSW, A, IX, IY, SP)
         */
        ramend = snapshot_buff[2] + 256 * snapshot_buff[3];
        if (2 + 2 + ramend + 1 + 16 + 16 + 16 + 1 + 1 + 16 + 16 + 64 + 7 != snapshot_size)
        {
            LOG(("microtan_snapshot_copy: size %d doesn't match RAM size %d + structure size\n", snapshot_size, ramend+1));
            return;
        }

        LOG(("microtan_snapshot_copy: size %d found, assuming *.M65 format\n", snapshot_size));
        base = 4;
        memcpy(RAM, &snapshot_buff[base], snapshot_buff[2] + 256 * snapshot_buff[3] + 1);
        base += ramend + 1;

        /* first set of VIA6522 registers */
        for (i = 0; i < 16; i++ )
            via_w(via_0, i, snapshot_buff[base++]);

        /* second set of VIA6522 registers */
        for (i = 0; i < 16; i++ )
            via_w(via_1, i, snapshot_buff[base++]);

        /* microtan IO bff0-bfff */
        for (i = 0; i < 16; i++ )
        {
            RAM[0xbff0+i] = snapshot_buff[base++];
            if (i < 4)
                microtan_bffx_w(space, i, RAM[0xbff0+i]);
        }

        microtan_sound_w(space, 0, snapshot_buff[base++]);
        microtan_chunky_graphics = snapshot_buff[base++];

        /* first set of AY8910 registers */
        for (i = 0; i < 16; i++ )
        {
            ay8910_address_w(ay8910, 0, i);
            ay8910_data_w(ay8910, 0, snapshot_buff[base++]);
        }

        /* second set of AY8910 registers */
        for (i = 0; i < 16; i++ )
        {
            ay8910_address_w(ay8910, 0, i);
            ay8910_data_w(ay8910, 0, snapshot_buff[base++]);
        }

        for (i = 0; i < 32*16; i++)
        {
            microtan_chunky_buffer[i] = (snapshot_buff[base+i/8] >> (i&7)) & 1;
        }
        base += 64;

        microtan_set_cpu_regs(machine, snapshot_buff, base);
    }
}

SNAPSHOT_LOAD( microtan )
{
	UINT8 *snapshot_buff;

	snapshot_buff = image_ptr(image);
	if (!snapshot_buff)
		return INIT_FAIL;

	if (microtan_varify_snapshot(snapshot_buff, snapshot_size)==IMAGE_VERIFY_FAIL)
		return INIT_FAIL;

	microtan_snapshot_copy(image->machine, snapshot_buff, snapshot_size);
	return INIT_PASS;
}

QUICKLOAD_LOAD( microtan_hexfile )
{
	int snapshot_size;
	UINT8 *snapshot_buff;
	char *buff;
	int rc;

	snapshot_size = 8263;   /* magic size */
	snapshot_buff = malloc(snapshot_size);
	if (!snapshot_buff)
	{
		LOG(("microtan_hexfile_load: could not allocate %d bytes of buffer\n", snapshot_size));
		return INIT_FAIL;
	}
	memset(snapshot_buff, 0, snapshot_size);

	buff = malloc(quickload_size + 1);
	if (!buff)
	{
		free(snapshot_buff);
		LOG(("microtan_hexfile_load: could not allocate %d bytes of buffer\n", quickload_size));
		return INIT_FAIL;
	}
	image_fread(image, buff, quickload_size);

    buff[quickload_size] = '\0';

    if (buff[0] == ':')
        rc = parse_intel_hex(snapshot_buff, buff);
	else
		rc = parse_zillion_hex(snapshot_buff, buff);
	if (rc == INIT_PASS)
		microtan_snapshot_copy(image->machine, snapshot_buff, snapshot_size);
	free(snapshot_buff);
	return rc;
}

DRIVER_INIT( microtan )
{
    UINT8 *dst = memory_region(machine, "gfx2");
    int i;
    const address_space *space = cpu_get_address_space(machine->cpu[0], ADDRESS_SPACE_PROGRAM);

    for (i = 0; i < 256; i++)
    {
        switch (i & 3)
        {
        case 0: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x00; break;
        case 1: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xf0; break;
        case 2: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x0f; break;
        case 3: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xff; break;
        }
        dst += 4;
        switch ((i >> 2) & 3)
        {
        case 0: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x00; break;
        case 1: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xf0; break;
        case 2: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x0f; break;
        case 3: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xff; break;
        }
        dst += 4;
        switch ((i >> 4) & 3)
        {
        case 0: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x00; break;
        case 1: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xf0; break;
        case 2: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x0f; break;
        case 3: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xff; break;
        }
        dst += 4;
        switch ((i >> 6) & 3)
        {
        case 0: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x00; break;
        case 1: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xf0; break;
        case 2: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0x0f; break;
        case 3: dst[ 0] = dst[ 1] = dst[ 2] = dst[ 3] = 0xff; break;
        }
        dst += 4;
    }

    switch (read_dsw(machine) & 3)
    {
        case 0:  // 1K only :)
            memory_install_read8_handler(space, 0x0400, 0xbbff, 0, 0, SMH_NOP);
            memory_install_write8_handler(space, 0x0400, 0xbbff, 0, 0, SMH_NOP);
            break;
        case 1:  // +7K TANEX
            memory_install_read8_handler(space, 0x0400, 0x1fff, 0, 0, SMH_RAM);
            memory_install_write8_handler(space, 0x0400, 0x1fff, 0, 0, SMH_RAM);
            memory_install_read8_handler(space, 0x2000, 0xbbff, 0, 0, SMH_NOP);
            memory_install_write8_handler(space, 0x2000, 0xbbff, 0, 0, SMH_NOP);
            break;
        default: // +7K TANEX + 40K TANRAM
            memory_install_read8_handler(space, 0x0400, 0xbbff, 0, 0, SMH_RAM);
            memory_install_write8_handler(space, 0x0400, 0xbbff, 0, 0, SMH_RAM);
            break;
    }

}

MACHINE_RESET( microtan )
{
    int i;
	static const char *const keynames[] = { "ROW0", "ROW1", "ROW2", "ROW3", "ROW4", "ROW5", "ROW6", "ROW7", "ROW8" };
	
	for (i = 1; i < 10;  i++)
	{
        keyrows[i] = input_port_read(machine, keynames[i-1]);
	}
    set_led_status(1, (keyrows[3] & 0x80) ? 0 : 1);

	microtan_timer = timer_alloc(machine, microtan_read_cassette, NULL);
}
