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

#include <sys/time.h>

#include "emulate.h"
#include "persistence.h"
#include "memory.h"
#include "options.h"
#include "romio.h"
#include "types.h"
#include "ui4x/api.h"

#define MCTL_MMIO_SX 0
#define MCTL_SysRAM_SX 1
#define MCTL_PORT1_SX 2
#define MCTL_PORT2_SX 3
#define MCTL_EXTRA_SX 4
#define MCTL_SysROM_SX 5

#define MCTL_MMIO_GX 0
#define MCTL_SysRAM_GX 1
#define MCTL_BANK_GX 2
#define MCTL_PORT1_GX 3
#define MCTL_PORT2_GX 4
#define MCTL_SysROM_GX 5

#define DISP_INSTR_OFF 0x10

long nibble_masks[ 16 ] = { 0x0000000f, 0x000000f0, 0x00000f00, 0x0000f000, 0x000f0000, 0x00f00000, 0x0f000000, 0xf0000000,
                            0x0000000f, 0x000000f0, 0x00000f00, 0x0000f000, 0x000f0000, 0x00f00000, 0x0f000000, 0xf0000000 };

display_t display;

void ( *bus_write_nibble )( Address addr, Nibble val );
Nibble ( *bus_fetch_nibble )( Address addr );
int ( *read_nibble_crc )( Address addr );

static int line_counter = -1;

static inline Nibble calc_crc( Nibble nib )
{
    saturn.crc = ( saturn.crc >> 4 ) ^ ( ( ( saturn.crc ^ nib ) & 0xf ) * 0x1081 );
    return nib;
}

static void write_dev_mem( Address addr, int val )
{
    static int old_line_offset = -1;

    device_check = true;
    schedule_event = 0;
    switch ( ( int )addr ) {
        case 0x100: /* DISPIO */
            if ( val != saturn.disp_io ) {
                saturn.disp_io = val;
                display.on = ( val & 0x8 ) >> 3;
                display.offset = val & 0x7;
                if ( display.offset > 3 )
                    display.nibs_per_line = ( NIBBLES_PER_ROW + saturn.line_offset + 2 ) & 0xfff;
                else
                    display.nibs_per_line = ( NIBBLES_PER_ROW + saturn.line_offset ) & 0xfff;
                display.disp_end = display.disp_start + ( display.nibs_per_line * ( display.lines + 1 ) );
            }
            return;
        case 0x101: /* CONTRAST CONTROL */
            saturn.contrast_ctrl = val;
            display.contrast &= ~0x0f;
            display.contrast |= val;
            return;
        case 0x102: /* CONTRAST CONTROL */
            display.contrast &= ~0xf0;
            display.contrast |= ( ( val & 0x1 ) << 4 );
            /* Fall through */
        case 0x103: /* DISPLAY TEST */
            saturn.disp_test &= ~nibble_masks[ addr - 0x102 ];
            saturn.disp_test |= val << ( ( addr - 0x102 ) * 4 );
            return;
        case 0x104:
        case 0x105:
        case 0x106:
        case 0x107: /* CRC */
            saturn.crc &= ~nibble_masks[ addr - 0x104 ];
            saturn.crc |= val << ( ( addr - 0x104 ) * 4 );
            return;
        case 0x108: /* POWER STATUS */
            saturn.power_status = val;
            return;
        case 0x109: /* POWER CONTROL */
            saturn.power_ctrl = val;
            return;
        case 0x10a: /* MODE */
            saturn.mode = val;
            return;
        case 0x10b:
        case 0x10c: /* ANNUNC */
            saturn.annunc &= ~nibble_masks[ addr - 0x10b ];
            saturn.annunc |= val << ( ( addr - 0x10b ) * 4 );
            return;
        case 0x10d: /* BAUD */
            saturn.baud = val;
            device.baud_touched = true;
            return;
        case 0x10e: /* CARD CONTROL */
            saturn.card_ctrl = val;
            if ( saturn.card_ctrl & 0x02 )
                saturn.st[ MP ] = 1;
            if ( saturn.card_ctrl & 0x01 )
                do_interupt();
            /* device.card_ctrl_touched = true; */
            return;
        case 0x10f: /* CARD STATUS */
            return;
        case 0x110: /* IO CONTROL */
            saturn.io_ctrl = val;
            device.ioc_touched = true;
            return;
        case 0x111: /* RCS */
            saturn.rcs = val;
            return;
        case 0x112: /* TCS */
            saturn.tcs = val;
            return;
        case 0x113: /* CRER */
            saturn.rcs &= 0x0b;
            return;
        case 0x114:
        case 0x115: /* RBR */
            return;
        case 0x116:
        case 0x117: /* TBR */
            saturn.tbr &= ~nibble_masks[ addr - 0x116 ];
            saturn.tbr |= val << ( ( addr - 0x116 ) * 4 );
            saturn.tcs |= 0x01;
            device.tbr_touched = true;
            return;
        case 0x118:
        case 0x119: /* SERVICE REQ */
            saturn.sreq &= ~nibble_masks[ addr - 0x118 ];
            saturn.sreq |= val << ( ( addr - 0x118 ) * 4 );
            /* device.sreq_touched = true; */
            return;
        case 0x11a: /* IR CONTROL */
            saturn.ir_ctrl = val;
            /* device.ir_ctrl_touched = true; */
            return;
        case 0x11b: /* BASE NIB OFFSET */
            saturn.base_off = val;
            /* device.base_off_touched = true; */
            return;
        case 0x11c: /* LED CONTROL */
            saturn.lcr = val;
            /* device.lcr_touched = true; */
            return;
        case 0x11d: /* LED BUFFER */
            saturn.lbr = val;
            /* device.lbr_touched = true; */
            return;
        case 0x11e: /* SCRATCH PAD */
            saturn.scratch = val;
            /* device.scratch_touched = true; */
            return;
        case 0x11f: /* BASENIBBLE */
            saturn.base_nibble = val;
            /* device.base_Nibbleouched = true; */
            return;
        case 0x120:
        case 0x121:
        case 0x122:
        case 0x123: /* DISP_ADDR */
        case 0x124:
            saturn.disp_addr &= ~nibble_masks[ addr - 0x120 ];
            saturn.disp_addr |= val << ( ( addr - 0x120 ) * 4 );
            if ( display.disp_start != ( saturn.disp_addr & 0xffffe ) ) {
                display.disp_start = saturn.disp_addr & 0xffffe;
                display.disp_end = display.disp_start + ( display.nibs_per_line * ( display.lines + 1 ) );
            }
            return;
        case 0x125:
        case 0x126:
        case 0x127: /* LINE_OFFSET */
            saturn.line_offset &= ~nibble_masks[ addr - 0x125 ];
            saturn.line_offset |= val << ( ( addr - 0x125 ) * 4 );
            if ( saturn.line_offset != old_line_offset ) {
                old_line_offset = saturn.line_offset;
                if ( display.offset > 3 )
                    display.nibs_per_line = ( NIBBLES_PER_ROW + saturn.line_offset + 2 ) & 0xfff;
                else
                    display.nibs_per_line = ( NIBBLES_PER_ROW + saturn.line_offset ) & 0xfff;
                display.disp_end = display.disp_start + ( display.nibs_per_line * ( display.lines + 1 ) );
            }
            return;
        case 0x128:
        case 0x129: /* LINE_COUNT */
            saturn.line_count &= ~nibble_masks[ addr - 0x128 ];
            saturn.line_count |= val << ( ( addr - 0x128 ) * 4 );
            line_counter = -1;
            if ( display.lines != ( saturn.line_count & 0x3f ) ) {
                display.lines = saturn.line_count & 0x3f;
                if ( display.lines == 0 )
                    display.lines = 63;
                display.disp_end = display.disp_start + ( display.nibs_per_line * ( display.lines + 1 ) );
            }
            return;
        case 0x12a:
        case 0x12b:
        case 0x12c:
        case 0x12d: /* Dont know yet */
            saturn.unknown &= ~nibble_masks[ addr - 0x12a ];
            saturn.unknown |= val << ( ( addr - 0x12a ) * 4 );
            /* device.unknown_touched = true; */
            return;
        case 0x12e: /* TIMER 1 CONTROL */
            saturn.t1_ctrl = val;
            /* device.t1_ctrl_touched = true; */
            return;
        case 0x12f: /* TIMER 2 CONTROL */
            saturn.t2_ctrl = val;
            /* device.t2_ctrl_touched = true; */
            return;
        case 0x130:
        case 0x131:
        case 0x132:
        case 0x133: /* MENU_ADDR */
        case 0x134:
            saturn.menu_addr &= ~nibble_masks[ addr - 0x130 ];
            saturn.menu_addr |= val << ( ( addr - 0x130 ) * 4 );
            if ( display.menu_start != saturn.menu_addr ) {
                display.menu_start = saturn.menu_addr;
                display.menu_end = display.menu_start + 0x110;
            }
            return;
        case 0x135:
        case 0x136: /* Dont know yet 2 */
            saturn.unknown2 &= ~nibble_masks[ addr - 0x135 ];
            saturn.unknown2 |= val << ( ( addr - 0x135 ) * 4 );
            /* device.unknown2_touched = true; */
            return;
        case 0x137: /* TIMER1 */
            saturn.timer1 = val;
            device.t1_touched = true;
            return;
        case 0x138:
        case 0x139:
        case 0x13a:
        case 0x13b:
        case 0x13c:
        case 0x13d:
        case 0x13e:
        case 0x13f: /* TIMER2 */
            saturn.timer2 &= ~nibble_masks[ addr - 0x138 ];
            saturn.timer2 |= val << ( ( addr - 0x138 ) * 4 );
            device.t2_touched = true;
            return;
        default:
            if ( config.verbose )
                fprintf( stderr, "%.5lx: UNKNOWN DEVICE WRITE AT 0x%lx !!!\n", saturn.pc, addr );
            return;
    }
}

static int read_dev_mem( Address addr )
{
    switch ( ( int )addr ) {
        case 0x100: /* DISPLAY IO */
            return saturn.disp_io & 0x0f;
        case 0x101: /* CONTRAST CONTROL */
            return saturn.contrast_ctrl & 0x0f;
        case 0x102:
        case 0x103: /* DISPLAY TEST */
            return ( saturn.disp_test >> ( ( addr - 0x102 ) * 4 ) ) & 0x0f;
        case 0x104:
        case 0x105:
        case 0x106:
        case 0x107: /* CRC */
            return ( saturn.crc >> ( ( addr - 0x104 ) * 4 ) ) & 0x0f;
        case 0x108: /* POWER STATUS */
            return saturn.power_status & 0x0f;
        case 0x109: /* POWER CONTROL */
            return saturn.power_ctrl & 0x0f;
        case 0x10a: /* MODE */
            return saturn.mode & 0x0f;
        case 0x10b:
        case 0x10c: /* ANNUNC */
            return ( saturn.annunc >> ( ( addr - 0x10b ) * 4 ) ) & 0x0f;
        case 0x10d: /* BAUD */
            return saturn.baud & 0x0f;
        case 0x10e: /* CARD CONTROL */
            return saturn.card_ctrl & 0x0f;
        case 0x10f: /* CARD STATUS */
            return saturn.card_status & 0x0f;
        case 0x110: /* IO CONTROL */
            return saturn.io_ctrl & 0x0f;
        case 0x111: /* RCS */
            return saturn.rcs & 0x0f;
        case 0x112: /* TCS */
            return saturn.tcs & 0x0f;
        case 0x113: /* CRER */
            return 0x00;
        case 0x114:
        case 0x115: /* RBR */
            saturn.rcs &= 0x0e;
            device.rbr_touched = true;
            device_check = true;
            schedule_event = 0;
            return ( saturn.rbr >> ( ( addr - 0x114 ) * 4 ) ) & 0x0f;
        case 0x116:
        case 0x117: /* TBR */
            return 0x00;
        case 0x118:
        case 0x119: /* SERVICE REQ */
            return ( saturn.sreq >> ( ( addr - 0x118 ) * 4 ) ) & 0x0f;
        case 0x11a: /* IR CONTROL */
            return saturn.ir_ctrl & 0x0f;
        case 0x11b: /* BASE NIB OFFSET */
            return saturn.base_off & 0x0f;
        case 0x11c: /* LED CONTROL */
            return saturn.lcr & 0x0f;
        case 0x11d: /* LED BUFFER */
            return saturn.lbr & 0x0f;
        case 0x11e: /* SCRATCH PAD */
            return saturn.scratch & 0x0f;
        case 0x11f: /* BASENIBBLE */
            return saturn.base_nibble & 0x0f;
        case 0x120:
        case 0x121:
        case 0x122:
        case 0x123: /* DISP_ADDR */
        case 0x124:
            return ( saturn.disp_addr >> ( ( addr - 0x120 ) * 4 ) ) & 0x0f;
        case 0x125:
        case 0x126:
        case 0x127: /* LINE_OFFSET */
            return ( saturn.line_offset >> ( ( addr - 0x125 ) * 4 ) ) & 0x0f;
        case 0x128:
        case 0x129: /* LINE_COUNT */
            line_counter++;
            if ( line_counter > 0x3f )
                line_counter = -1;
            return ( ( ( saturn.line_count & 0xc0 ) | ( line_counter & 0x3f ) ) >> ( ( addr - 0x128 ) * 4 ) ) & 0x0f;
        case 0x12a:
        case 0x12b:
        case 0x12c:
        case 0x12d: /* Dont know yet */
            return ( saturn.unknown >> ( ( addr - 0x12a ) * 4 ) ) & 0x0f;
        case 0x12e: /* TIMER 1 CONTROL */
            return saturn.t1_ctrl & 0x0f;
        case 0x12f: /* TIMER 2 CONTROL */
            return saturn.t2_ctrl & 0x0f;
        case 0x130:
        case 0x131:
        case 0x132:
        case 0x133: /* MENU_ADDR */
        case 0x134:
            return ( saturn.menu_addr >> ( ( addr - 0x130 ) * 4 ) ) & 0x0f;
        case 0x135:
        case 0x136: /* Dont know yet 2 */
            return ( saturn.unknown2 >> ( ( addr - 0x135 ) * 4 ) ) & 0x0f;
        case 0x137:
            return saturn.timer1 & 0xf;
        case 0x138:
        case 0x139:
        case 0x13a:
        case 0x13b:
        case 0x13c:
        case 0x13d:
        case 0x13e:
        case 0x13f:
            return ( saturn.timer2 >> ( ( addr - 0x138 ) * 4 ) ) & 0xf;
        default:
            if ( config.verbose )
                fprintf( stderr, "%.5lx: UNKNOWN DEVICE READ AT 0x%lx !!!\n", saturn.pc, addr );
            return 0x00;
    }
}

static void bus_write_nibble_sx( Address addr, Nibble val )
{
    addr &= 0xfffff;
    val &= 0x0f;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 && saturn.mem_cntl[ MCTL_MMIO_SX ].config[ 0 ] == 0x100 ) {
                write_dev_mem( addr, val );
                return;
            }
            return;
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
            return;
        case 7:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0x70000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfc000 && addr < 0x74000 ) {
                    saturn.ram[ addr - 0x70000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfe000 && addr < 0x72000 ) {
                    saturn.ram[ addr - 0x70000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xf0000 ) {
                    saturn.ram[ addr - 0x70000 ] = val;
                    break;
                }
            }
            return;
        case 8:
        case 9:
        case 0xa:
        case 0xb:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0x80000 ) {
                if ( port1_is_ram )
                    saturn.port1[ ( addr - 0x80000 ) & port1_mask ] = val;
                return;
            }
            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0x80000 ) {
                if ( port2_is_ram )
                    saturn.port2[ ( addr - 0x80000 ) & port2_mask ] = val;
                return;
            }
            return;
        case 0xc:
        case 0xd:
        case 0xe:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xc0000 ) {
                if ( port1_is_ram )
                    saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] = val;
                return;
            }
            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xc0000 ) {
                if ( port2_is_ram )
                    saturn.port2[ ( addr - 0xc0000 ) & port2_mask ] = val;
                return;
            }
            return;
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0xf0000 ) {
                saturn.ram[ addr - 0xf0000 ] = val;
                break;
            }
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xc0000 ) {
                if ( port1_is_ram )
                    saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] = val;
                return;
            }
            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xc0000 ) {
                if ( port2_is_ram )
                    saturn.port2[ ( addr - 0xc0000 ) & port2_mask ] = val;
                return;
            }
            return;
    }
}

static void bus_write_nibble_gx( Address addr, Nibble val )
{
    addr &= 0xfffff;
    val &= 0x0f;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 && saturn.mem_cntl[ MCTL_MMIO_GX ].config[ 0 ] == 0x100 ) {
                write_dev_mem( addr, val );
                return;
            }
            return;
        case 1:
        case 2:
        case 3:
        case 5:
        case 6:
            return;
        case 4:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x40000 ) {
                saturn.ram[ addr - 0x40000 ] = val;
                break;
            }
            return;
        case 7:
            if ( addr >= 0x7f000 && saturn.mem_cntl[ MCTL_BANK_GX ].config[ 0 ] == 0x7f000 ) {
                return;
            }
            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0x7e000 ) {
                return;
            }
            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0x7e000 ) {
                return;
            }
            return;
        case 8:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0x84000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0x82000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xf0000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
            }
            return;
        case 9:
            if ( saturn.mem_cntl[ MCTL_BANK_GX ].config[ 0 ] == 0x90000 ) {
                if ( addr < 0x91000 ) {
                    return;
                }
            }
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
            return;
        case 0xa:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xa0000 ) {
                if ( port1_is_ram )
                    saturn.port1[ ( addr - 0xa0000 ) & port1_mask ] = val;
                return;
            }
            return;
        case 0xb:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ) {
                    saturn.ram[ addr - 0x80000 ] = val;
                    break;
                }
            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xb0000 ) {
                if ( port2_is_ram )
                    saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xb0000 ) ) & port2_mask ] = val;
                return;
            }
            return;
        case 0xc:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0xc4000 ) {
                    saturn.ram[ addr - 0xc0000 ] = val;
                    break;
                }
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0xc2000 ) {
                    saturn.ram[ addr - 0xc0000 ] = val;
                    break;
                }
                saturn.ram[ addr - 0xc0000 ] = val;
                break;
            }
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 ) {
                if ( port1_is_ram )
                    saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] = val;
                return;
            }
            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 ) {
                if ( port2_is_ram )
                    saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ] = val;
                return;
            }
            return;
        case 0xd:
        case 0xe:
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ) {
                    saturn.ram[ addr - 0xc0000 ] = val;
                    break;
                }
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 )
                if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 1 ] == 0xc0000 ) {
                    if ( port1_is_ram )
                        saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] = val;
                    return;
                }
            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 )
                if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 1 ] == 0xc0000 ) {
                    if ( port2_is_ram )
                        saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ] = val;
                    return;
                }
            return;
    }
}

static Nibble read_nibble_sx( Address addr )
{
    addr &= 0xfffff;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 ) {
                if ( saturn.mem_cntl[ MCTL_MMIO_SX ].config[ 0 ] == 0x100 )
                    return read_dev_mem( addr );
                else
                    return 0x00;
            }
            return saturn.rom[ addr ];
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
            return saturn.rom[ addr ];
        case 7:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0x70000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfc000 && addr < 0x74000 )
                    return saturn.ram[ addr - 0x70000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfe000 && addr < 0x72000 )
                    return saturn.ram[ addr - 0x70000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xf0000 )
                    return saturn.ram[ addr - 0x70000 ];
            }
            return saturn.rom[ addr ];
        case 8:
        case 9:
        case 0xa:
        case 0xb:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0x80000 )
                return saturn.port1[ ( addr - 0x80000 ) & port1_mask ];

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0x80000 )
                return saturn.port2[ ( addr - 0x80000 ) & port2_mask ];

            return 0x00;
        case 0xc:
        case 0xd:
        case 0xe:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xc0000 )
                return saturn.port1[ ( addr - 0xc0000 ) & port1_mask ];

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xc0000 )
                return saturn.port2[ ( addr - 0xc0000 ) & port2_mask ];

            return 0x00;
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0xf0000 )
                return saturn.ram[ addr - 0xf0000 ];
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xf0000 )
                return saturn.port1[ ( addr - 0xf0000 ) & port1_mask ];

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xf0000 )
                return saturn.port2[ ( addr - 0xf0000 ) & port2_mask ];

            return 0x00;
    }
    return 0x00;
}

static Nibble read_nibble_gx( Address addr )
{
    addr &= 0xfffff;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 ) {
                if ( saturn.mem_cntl[ 0 ].config[ 0 ] == 0x100 )
                    return read_dev_mem( addr );
                else
                    return 0x00;
            }
            return saturn.rom[ addr ];
        case 1:
        case 2:
        case 3:
        case 5:
        case 6:
            return saturn.rom[ addr ];
        case 4:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x40000 )
                return saturn.ram[ addr - 0x40000 ];
            return saturn.rom[ addr ];
        case 7:
            if ( addr >= 0x7f000 && saturn.mem_cntl[ MCTL_BANK_GX ].config[ 0 ] == 0x7f000 ) {
                if ( addr == 0x7f000 )
                    saturn.bank_switch = 0;

                if ( addr >= 0x7f040 && addr < 0x7f080 )
                    saturn.bank_switch = ( addr - 0x7f040 ) / 2;

                return 0x7;
            }
            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0x7e000 )
                return 0x7;

            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0x7e000 )
                return 0x7;

            return saturn.rom[ addr ];
        case 8:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0x84000 )
                    return saturn.ram[ addr - 0x80000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0x82000 )
                    return saturn.ram[ addr - 0x80000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xf0000 )
                    return saturn.ram[ addr - 0x80000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return saturn.ram[ addr - 0x80000 ];
            }
            return saturn.rom[ addr ];
        case 9:
            if ( saturn.mem_cntl[ 0 ].config[ 0 ] == 0x90000 ) {
                if ( addr < 0x91000 ) {
                    if ( addr == 0x90000 )
                        saturn.bank_switch = 0;

                    if ( addr >= 0x90040 && addr < 0x90080 )
                        saturn.bank_switch = ( addr - 0x90040 ) / 2;

                    return 0x7;
                }
            }
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return saturn.ram[ addr - 0x80000 ];
            return saturn.rom[ addr ];
        case 0xa:
            if (( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                && ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ))
                    return saturn.ram[ addr - 0x80000 ];
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xa0000 )
                return saturn.port1[ ( addr - 0xa0000 ) & port1_mask ];

            return saturn.rom[ addr ];
        case 0xb:
            if (( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                && ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ))
                    return saturn.ram[ addr - 0x80000 ];
            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xb0000 ) {
                return saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xb0000 ) ) & port2_mask ];
                /*
                            if (port2_size > (saturn.bank_switch << 18))
                              {
                                return saturn.port2[(saturn.bank_switch << 18)
                                                    + (addr - 0xb0000)];
                              }
                            return 0x00;
                */
            }
            return saturn.rom[ addr ];
        case 0xc:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0xc4000 )
                    return saturn.ram[ addr - 0xc0000 ];
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0xc2000 )
                    return saturn.ram[ addr - 0xc0000 ];
                return saturn.ram[ addr - 0xc0000 ];
            }
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 )
                return saturn.port1[ ( addr - 0xc0000 ) & port1_mask ];

            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 ) {
                return saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ];
                /*
                            if (port2_size > (saturn.bank_switch << 18))
                              {
                                return saturn.port2[(saturn.bank_switch << 18)
                                                    + (addr - 0xc0000)];
                              }
                            return 0x00;
                */
            }
            return saturn.rom[ addr ];
        case 0xd:
        case 0xe:
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return saturn.ram[ addr - 0xc0000 ];
            if (( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 )
                && ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 1 ] == 0xc0000 ) )
                    return saturn.port1[ ( addr - 0xc0000 ) & port1_mask ];

            if (( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 )
                && ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 1 ] == 0xc0000 )) {
                    return saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ];
                    /*
                                  if (port2_size > (saturn.bank_switch << 18))
                                    {
                                      return saturn.port2[(saturn.bank_switch <<
                       18)
                                                          + (addr - 0xc0000)];
                                    }
                                  return 0x00;
                    */
                }
            return saturn.rom[ addr ];
    }
    return 0x00;
}

static int read_nibble_crc_sx( Address addr )
{
    addr &= 0xfffff;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 ) {
                if ( saturn.mem_cntl[ MCTL_MMIO_SX ].config[ 0 ] == 0x100 )
                    return read_dev_mem( addr );
                else
                    return calc_crc( 0x00 );
            }
            return calc_crc( saturn.rom[ addr ] );
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
            return calc_crc( saturn.rom[ addr ] );
        case 7:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0x70000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfc000 && addr < 0x74000 )
                    return calc_crc( saturn.ram[ addr - 0x70000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xfe000 && addr < 0x72000 )
                    return calc_crc( saturn.ram[ addr - 0x70000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 1 ] == 0xf0000 )
                    return calc_crc( saturn.ram[ addr - 0x70000 ] );
            }
            return calc_crc( saturn.rom[ addr ] );
        case 8:
        case 9:
        case 0xa:
        case 0xb:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0x80000 )
                return calc_crc( saturn.port1[ ( addr - 0x80000 ) & port1_mask ] );

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0x80000 )
                return calc_crc( saturn.port2[ ( addr - 0x80000 ) & port2_mask ] );

            return 0x00;
        case 0xc:
        case 0xd:
        case 0xe:
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xc0000 )
                return calc_crc( saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] );

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xc0000 )
                return calc_crc( saturn.port2[ ( addr - 0xc0000 ) & port2_mask ] );

            return 0x00;
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_SX ].config[ 0 ] == 0xf0000 )
                return calc_crc( saturn.ram[ addr - 0xf0000 ] );
            if ( saturn.mem_cntl[ MCTL_PORT1_SX ].config[ 0 ] == 0xc0000 )
                return calc_crc( saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] );

            if ( saturn.mem_cntl[ MCTL_PORT2_SX ].config[ 0 ] == 0xc0000 )
                return calc_crc( saturn.port2[ ( addr - 0xc0000 ) & port2_mask ] );

            return 0x00;
    }
    return 0x00;
}

static int read_nibble_crc_gx( Address addr )
{
    addr &= 0xfffff;
    switch ( ( int )( addr >> 16 ) & 0x0f ) {
        case 0:
            if ( addr < 0x140 && addr >= 0x100 ) {
                if ( saturn.mem_cntl[ MCTL_MMIO_GX ].config[ 0 ] == 0x100 )
                    return read_dev_mem( addr );
                else
                    return calc_crc( 0x00 );
            }
            return calc_crc( saturn.rom[ addr ] );
        case 1:
        case 2:
        case 3:
        case 5:
        case 6:
            return calc_crc( saturn.rom[ addr ] );
        case 4:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x40000 )
                return calc_crc( saturn.ram[ addr - 0x40000 ] );
            return calc_crc( saturn.rom[ addr ] );
        case 7:
            if ( addr >= 0x7f000 && saturn.mem_cntl[ MCTL_BANK_GX ].config[ 0 ] == 0x7f000 ) {
                if ( addr == 0x7f000 )
                    saturn.bank_switch = 0;

                if ( addr >= 0x7f040 && addr < 0x7f080 )
                    saturn.bank_switch = ( addr - 0x7f040 ) / 2;

                return 0x7;
            }
            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0x7e000 )
                return 0x7;

            if ( addr >= 0x7e000 && addr < 0x7f000 && saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0x7e000 )
                return 0x7;

            return calc_crc( saturn.rom[ addr ] );
        case 8:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0x84000 )
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0x82000 )
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xf0000 )
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
            }
            return calc_crc( saturn.rom[ addr ] );
        case 9:
            if ( saturn.mem_cntl[ 0 ].config[ 0 ] == 0x90000 ) {
                if ( addr < 0x91000 ) {
                    if ( addr == 0x90000 )
                        saturn.bank_switch = 0;

                    if ( addr >= 0x90040 && addr < 0x90080 )
                        saturn.bank_switch = ( addr - 0x90040 ) / 2;

                    return 0x7;
                }
            }
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
            return calc_crc( saturn.rom[ addr ] );
        case 0xa:
            if (( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                && ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ))
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xa0000 )
                return calc_crc( saturn.port1[ ( addr - 0xa0000 ) & port1_mask ] );

            return calc_crc( saturn.rom[ addr ] );
        case 0xb:
            if (( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0x80000 )
                && ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 ))
                    return calc_crc( saturn.ram[ addr - 0x80000 ] );
            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xb0000 ) {
                return calc_crc( saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xb0000 ) ) & port2_mask ] );
                /*
                            if (port2_size > (saturn.bank_switch << 18))
                              {
                                return calc_crc(saturn.port2[(saturn.bank_switch
                   << 18)
                                                             + (addr -
                   0xb0000)]);
                              }
                            return 0x00;
                */
            }
            return calc_crc( saturn.rom[ addr ] );
        case 0xc:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 ) {
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfc000 && addr < 0xc4000 )
                    return calc_crc( saturn.ram[ addr - 0xc0000 ] );
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xfe000 && addr < 0xc2000 )
                    return calc_crc( saturn.ram[ addr - 0xc0000 ] );
                return calc_crc( saturn.ram[ addr - 0xc0000 ] );
            }
            if ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 )
                return calc_crc( saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] );

            if ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 ) {
                return calc_crc( saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ] );
                /*
                            if (port2_size > (saturn.bank_switch << 18))
                              {
                                return calc_crc(saturn.port2[(saturn.bank_switch
                   << 18)
                                                             + (addr -
                   0xc0000)]);
                              }
                            return 0x00;
                */
            }
            return calc_crc( saturn.rom[ addr ] );
        case 0xd:
        case 0xe:
        case 0xf:
            if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 0 ] == 0xc0000 )
                if ( saturn.mem_cntl[ MCTL_SysRAM_GX ].config[ 1 ] == 0xc0000 )
                    return calc_crc( saturn.ram[ addr - 0xc0000 ] );
            if (( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 0 ] == 0xc0000 )
                && ( saturn.mem_cntl[ MCTL_PORT1_GX ].config[ 1 ] == 0xc0000 ) )
                    return calc_crc( saturn.port1[ ( addr - 0xc0000 ) & port1_mask ] );

            if (( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 0 ] == 0xc0000 )
                && ( saturn.mem_cntl[ MCTL_PORT2_GX ].config[ 1 ] == 0xc0000 )) {
                    return calc_crc( saturn.port2[ ( ( saturn.bank_switch << 18 ) + ( addr - 0xc0000 ) ) & port2_mask ] );
                    /*
                                  if (port2_size > (saturn.bank_switch << 18))
                                    {
                                      return
                       calc_crc(saturn.port2[(saturn.bank_switch << 18)
                                                                   + (addr -
                       0xc0000)]);
                                    }
                                  return 0x00;
                    */
                }
            return calc_crc( saturn.rom[ addr ] );
    }
    return 0x00;
}

long read_nibbles( Address addr, int len )
{
    long val = 0;

    addr += len;
    while ( len-- > 0 )
        val = ( val << 4 ) | bus_fetch_nibble( --addr );

    return val;
}

void dev_memory_init( void )
{
    if ( opt_gx ) {
        bus_fetch_nibble = read_nibble_gx;
        read_nibble_crc = read_nibble_crc_gx;
        bus_write_nibble = bus_write_nibble_gx;
    } else {
        bus_fetch_nibble = read_nibble_sx;
        read_nibble_crc = read_nibble_crc_sx;
        bus_write_nibble = bus_write_nibble_sx;
    }

    memset( &device, 0, sizeof( device ) );
}
