/******************************************************************************
*
* Copyright (C) 2019  Brian Berlin
* 
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************
*
* FILENAME: test_states.c
*
* DESCRIPTION:  This contains tests for the saving and recalling states
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.9.0   12/17/19  bberlin      Creation
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/stat.h>
#include "../src/interface/states.h"
#include "../src/interface/util.h"
#include "../src/interface/logger.h"
#include "../src/core/console.h"
#include "../src/core/gtia.h"
#include "../src/core/cart.h"
#include "../src/core/antic.h"
#include "../src/core/pokey.h"
#include "../src/core/pokeysound.h"
#include "../src/core/sio.h"
#include "../src/core/boom6502.h"
#include "../src/core/memory.h"

int failure = 0;

extern unsigned int pokey_cpu_clock;
extern t_cpu cpu6502;
extern t_gtia gtia;
extern t_antic antic;
extern t_pokey pokey;
extern t_sio_interface sio;
extern t_pia pia;
extern ps_chip pokeyChip[MAXPOKEYS];

extern KBYTE memory[0x10000];

static int g_fail_only = 0;

void output_result ( int result, const char *test, const char *value, 
        const char *error ) {

    if ( !result && !g_fail_only ) {
        printf ( "\e[32m[PASS]\e[m %s Result=%s\n", test, value );
    }
    else {
        failure++;
        printf ( "\e[31m[FAIL]\e[m %s %s Result=%s\n", test, error, value );
    }

}

int string_compare ( char *str1, char *str2 ) {

    if ( !str1 && !str2 )
        return 0;

    if ( !str1 || !str2 )
        return 1;

    return strcmp ( str1, str2 );

}

int mem_compare ( void *str1, void *str2, int length ) {

    if ( !str1 && !str2 )
        return 0;

    if ( !str1 || !str2 )
        return 1;

    return memcmp ( str1, str2, length );

}

static int run_large_cart_bank_state_round_trip ( t_atari *atari, int bank_size,
        const char *state_name, const char *label ) {

    KBYTE *bank;
    KBYTE *bank_orig;
    KBYTE visible_orig[0x8000];
    unsigned int portb, mem_image, be_switch;
    int i;
    int selected_bank = (bank_size / 0x8000) - 2;
    int result = 0;
    int size_result, bank_result, visible_result, switch_result;

    memory_init_bank ( bank_size );
    bank = mem_get_bank_image();
    bank_orig = malloc ( bank_size * sizeof(KBYTE) );
    if ( !bank || !bank_orig )
        return 1;

    for ( i = 0; i < bank_size; ++i )
        bank[i] = (KBYTE)((i / 0x8000) & 0xff);

    memcpy ( &memory[0x4000], &bank[selected_bank * 0x8000], 0x8000 );
    memcpy ( bank_orig, bank, bank_size );
    memcpy ( visible_orig, &memory[0x4000], sizeof(visible_orig) );

    atari->machine_type = MACHINE_TYPE_5200;
    atari->cart->media_info.mapping = CART_32K_BE;
    atari->cart->media_info.image_size = bank_size;
    atari->main_media = &atari->cart->media_info;
    mem_set_state_values ( 0x55, 0x66, selected_bank );

    result += states_save ( state_name, atari );

    memset ( bank, 0, bank_size );
    memset ( &memory[0x4000], 0, 0x8000 );
    mem_set_state_values ( 0, 0, 0 );

    result += states_recall ( state_name, atari );
    bank = mem_get_bank_image();
    mem_get_state_values ( &portb, &mem_image, &be_switch );

    size_result = (mem_get_bank_image_size() != bank_size) ? 1 : 0;
    bank_result = memcmp ( bank, bank_orig, bank_size ) ? 1 : 0;
    visible_result = memcmp ( &memory[0x4000], visible_orig, sizeof(visible_orig) ) ? 1 : 0;
    switch_result = (be_switch != (unsigned int)selected_bank) ? 1 : 0;
    result += size_result + bank_result + visible_result + switch_result;

    if ( result ) {
        printf ( "Large Cart Diagnostics %s: size=%d bank=%d visible=%d switch=%d restored_size=%d restored_switch=%u\n",
                label, size_result, bank_result, visible_result, switch_result,
                mem_get_bank_image_size(), be_switch );
    }

    output_result ( result, "Large Cart Bank State", label,
            "Bank contents, size, visible window, or selected bank did not round-trip" );

    free ( bank_orig );

    return result;

}

/* HYB_BANK_MODE_* are private to memory.c; mirror the enum order here. */
#define TEST_HYB_MODE_8K   0
#define TEST_HYB_MODE_16K  1

static int run_hybrid_state_round_trip ( t_atari *atari, int hyb_mode,
        int bank_index, const char *state_name, const char *label ) {

    const int bank_size = 0x80000;          /* 512K hybrid */
    const int sd_size = 0x40;
    const KBYTE bank_byte = 0xab;
    const int sd_cmd_count = 7;
    const int sd_bfff_phase = 1;
    KWORD addr;
    unsigned int bank_index_in_image;
    KBYTE *bank, *mem_img, *sd_img;
    KBYTE mem_img_orig[0x4000];
    unsigned char sd_orig[0x40];          /* SD backing is filled byte-wise */
    KBYTE sd_cmd_orig[42];
    int mem_img_size, i;
    int got_mode, got_b8, got_b16, got_count, got_phase;
    KBYTE got_cmd[42];
    int read_result, mode_result, bank_state_result, mem_img_result;
    int sd_img_result, sd_cmd_result;
    int result = 0;

    /* Build the hybrid bank image with a known byte in the active window. */
    memory_init_bank ( bank_size );
    bank = mem_get_bank_image();
    if ( !bank )
        return 1;

    mem_set_hyb_state ( hyb_mode,
                        hyb_mode == TEST_HYB_MODE_8K ? bank_index : 0,
                        hyb_mode == TEST_HYB_MODE_16K ? bank_index : 0 );

    if ( hyb_mode == TEST_HYB_MODE_8K ) {
        addr = 0x6005;
        bank_index_in_image = bank_index * 0x2000 + ( addr & 0x1FFF );
        /* exercise the SRAM write path (8K mode only) */
        write_5200_sram_hyb ( addr, bank_byte );
    }
    else {
        addr = 0x4005;
        bank_index_in_image = bank_index * 0x4000 + ( addr & 0x3FFF );
        bank[bank_index_in_image] = bank_byte;
    }

    /* Hidden RAM behind cart - distinct bytes. */
    mem_img = mem_get_mem_image();
    mem_img_size = mem_get_mem_image_size();
    for ( i = 0; i < mem_img_size; ++i )
        mem_img[i] = (KBYTE)((i * 7 + 0x11) & 0xff);
    memcpy ( mem_img_orig, mem_img, mem_img_size * sizeof(KBYTE) );

    /* SD backing image and command machine. */
    for ( i = 0; i < sd_size; ++i )
        sd_orig[i] = (KBYTE)((i * 3 + 0x80) & 0xff);
    memory_init_sd ( sd_size );
    memory_write_sd_bytes ( 0, sd_orig, sd_size );
    for ( i = 0; i < 42; ++i )
        sd_cmd_orig[i] = (KBYTE)((i + 0x40) & 0xff);
    mem_set_sd_cmd_state ( sd_cmd_orig, sd_cmd_count, sd_bfff_phase );

    atari->machine_type = MACHINE_TYPE_5200;
    atari->cart->media_info.mapping = CART_512K_HYBRID;
    atari->cart->media_info.image_size = bank_size;
    atari->main_media = &atari->cart->media_info;
    mem_set_state_values ( 0x55, 0, 0xf );

    result += states_save ( state_name, atari );

    /* Wipe everything the state should restore. */
    memory_init_bank ( bank_size );
    memset ( mem_get_mem_image(), 0, mem_img_size );
    memory_clear_sd ();
    mem_set_hyb_state ( TEST_HYB_MODE_8K, 0, 0 );
    mem_set_sd_cmd_state ( (KBYTE[42]){ 0 }, 0, 0 );

    result += states_recall ( state_name, atari );

    bank = mem_get_bank_image();
    mem_img = mem_get_mem_image();
    sd_img = mem_get_sd_image();
    mem_get_hyb_state ( &got_mode, &got_b8, &got_b16 );
    mem_get_sd_cmd_state ( got_cmd, &got_count, &got_phase );

    read_result = ( read_5200_hyb_banked ( addr ) != bank_byte ) ? 1 : 0;
    mode_result = ( got_mode != hyb_mode ) ? 1 : 0;
    if ( hyb_mode == TEST_HYB_MODE_8K )
        bank_state_result = ( got_b8 != bank_index ) ? 1 : 0;
    else
        bank_state_result = ( got_b16 != bank_index ) ? 1 : 0;
    mem_img_result = memcmp ( mem_img, mem_img_orig,
                              mem_img_size * sizeof(KBYTE) ) ? 1 : 0;
    sd_img_result = 0;
    if ( !sd_img )
        sd_img_result = 1;
    else
        for ( i = 0; i < sd_size; ++i )
            if ( (KBYTE)sd_orig[i] != sd_img[i] )
                sd_img_result = 1;
    sd_cmd_result = ( memcmp ( got_cmd, sd_cmd_orig, 42 ) ||
                      got_count != sd_cmd_count ||
                      got_phase != sd_bfff_phase ) ? 1 : 0;
    result += read_result + mode_result + bank_state_result +
              mem_img_result + sd_img_result + sd_cmd_result;

    if ( result ) {
        printf ( "Hybrid Diagnostics %s: read=%d mode=%d bank=%d mem_img=%d sd_img=%d sd_cmd=%d\n",
                label, read_result, mode_result, bank_state_result,
                mem_img_result, sd_img_result, sd_cmd_result );
    }

    output_result ( result, "Hybrid Cart State", label,
            "Active bank, hidden RAM, SD image, or SD command state did not round-trip" );

    memory_clear_sd ();

    return result;

}

/******************************************************************************
**  Function   :  main
**                                                
**  Objective  :  This is the main used to test the state module
**                                               
**  Parameters :  NONE                          
**                                             
**  return     :  0 if success                
**                -1 if failure               
******************************************************************************/ 
int main ( int argc, char *argv[]  ) {

    int line_no, error, result, i, j;
    int main_result;
    unsigned int portb, mem_image, be_switch;
    unsigned int portbOrig, mem_imageOrig;
    unsigned int pokey_cpu_clockOrig;
    char err_str[75];
    char *result_value;
    char *temp_file;
    const char *node_name;
    FILE *fp1, *fp2;
    t_atari myAtari;
    t_atari origAtari;
    t_cart cart;
    t_cart cartOrig;
    t_cart cart_right;
    t_cart cart_rightOrig;
    KBYTE xe_bankOrig[0x10000];
    KBYTE osOrig[0x4000];
    KBYTE basicOrig[0x2000];
    KBYTE memoryOrig[0x10000];
    KBYTE *bank;
    KBYTE bankOrig[0x20000];

    t_cpu cpuOrig;
    t_antic anticOrig;
    t_gtia gtiaOrig;
    t_pokey pokeyOrig;
    t_pia piaOrig;
    t_sio_interface sioOrig;
    ps_chip pokeyChipOrig[MAXPOKEYS];

    myAtari.cpu = &cpu6502;
    myAtari.antic = &antic;
    myAtari.gtia = &gtia;
    myAtari.pokey = &pokey;
    myAtari.pia = &pia;
    myAtari.sio = &sio;
    myAtari.cart = &cart;
    myAtari.cart_right = &cart_right;

    origAtari.cpu = &cpuOrig;
    origAtari.antic = &anticOrig;
    origAtari.gtia = &gtiaOrig;
    origAtari.pokey = &pokeyOrig;
    origAtari.pia = &piaOrig;
    origAtari.sio = &sioOrig;
    origAtari.cart = &cartOrig;
    origAtari.cart_right = &cart_rightOrig;

    console_init();
    mem_init();
    bank = mem_get_bank_image();
    sio_init();
    logger_init();

    /* Create directory to hold state files */
    mkdir("states", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
        
    /*
     * Get all of our values ready
     */
    myAtari.machine_type = MACHINE_TYPE_5200;
    if ( argc > 1 ) {
        if ( strstr(argv[1], "800") )
            myAtari.machine_type = MACHINE_TYPE_800;
        else if ( strstr(argv[1], "XL") )
            myAtari.machine_type = MACHINE_TYPE_XL;
    }

    myAtari.frame_number = 9876;
    myAtari.ram_size = 48;
    myAtari.tv_standard = 4;
    myAtari.basic_enable = 3;
    myAtari.option_key_pressed = 1;
    myAtari.start_key_pressed = 2;

    for ( i = 0; i < 0x10000; ++i )
        memory[i] = rand() & 0xff;
    mem_set_state_values ( portb = 20, mem_image = 34, be_switch=4 );
    for ( i = 0; i < 0x10000; ++i )
        bank[i] = rand() & 0xff;
    for ( i = 0; i < 0x10000; ++i ) {
        write_xe_bank_state(i, i & 0xff);
    }
    for ( i = 0; i < 0x4000; ++i ) {
        write_os_rom(i, i & 0xff);
    }
    for ( i = 0; i < 0x2000; ++i ) {
        write_basic_rom(i, i & 0xff);
    }

    cart.loaded = 1;

    util_alloc_string ( &cart.media_info.title, "TestCart" );
    util_alloc_string ( &cart.media_info.file, "/usr/share/games/testcart.bin" );
    cart.media_info.media_type = CART_4K;
    cart.media_info.mapping = 10;
    cart.media_info.image_size = 32768;
    cart.media_info.write_protect = 2;
    cart.media_info.crc = 0xf8903456;
    cart.media_info.collections = 0x12345678;
    util_alloc_string ( &cart.media_info.md5, "12345678123456781234567812345678");
    util_alloc_string ( &cart.media_info.sha1, "1234567812345678123456781234567887654321");

    util_alloc_string ( &cart_right.media_info.title, "TestCartRight" );
    util_alloc_string ( &cart_right.media_info.file, "/usr/share/games/testcartright.bin" );
    cart_right.media_info.media_type = CART_8K;
    cart_right.media_info.mapping = 12;
    cart_right.media_info.image_size = 16128;
    cart_right.media_info.write_protect = 1;
    cart_right.media_info.crc = 0x98903456;
    cart_right.media_info.collections = 0x14345678;
    util_alloc_string ( &cart_right.media_info.md5, "12345678123456781234567812345678");
    util_alloc_string ( &cart_right.media_info.sha1, "1234567812345678123456781234567887654321");

    cpu6502.i_req = 12;
    cpu6502.irq_assert = 3;
    cpu6502.i_cnt = 98;
    cpu6502.status = 141;
    cpu6502.pc = 6543;
    cpu6502.a_reg = 134;
    cpu6502.x_reg = 23;
    cpu6502.y_reg = 98;
    for ( i = 0; i < 0x100; ++i )
        cpu6502.stack[i] = 0xf;
    cpu6502.stack[0] = 10;
    cpu6502.stack[20] = 234;
    cpu6502.stack[100] = 27;
    cpu6502.stack_ptr = 255;

    antic.dmactl_reg = 0x25;
    antic.chactl_reg = 0x3f;
    antic.dlistl_reg = 0x12;
    antic.dlisth_reg = 0x68;
    antic.hscroll_reg_value = 0x1e;
    antic.vscroll_reg_value = 0x23;
    antic.pm_base_address = 0x4800;
    antic.ch_base_address = 0x7100;
    antic.nmi_status_reg = 0x1f;
    antic.nmi_dli_en = 0x80;
    antic.nmi_vbi_en = 0x40;
    antic.nmi_reset_en = 0x20;
    antic.vcount = 192;
    antic.msc = 0x4910;

    /* we double the value in the write register so go ahead and do that */
    gtia.hpos_p0_reg = 0x01<<1 ;
    gtia.hpos_p1_reg = 0x02<<1 ;
    gtia.hpos_p2_reg = 0x03<<1 ;
    gtia.hpos_p3_reg = 0x04<<1 ;
    gtia.hpos_m0_reg = 0x05<<1 ;
    gtia.hpos_m1_reg = 0x06<<1 ;
    gtia.hpos_m2_reg = 0x07<<1 ;
    gtia.hpos_m3_reg = 0x08<<1 ;

    gtia.w_reg[SIZEP0] = 0x09 ;
    gtia.w_reg[SIZEP1] = 0x0a ;
    gtia.w_reg[SIZEP2] = 0x0b ;
    gtia.w_reg[SIZEP3] = 0x0c ;
    gtia.w_reg[SIZEM] = 0x0e ;
    gtia.graph_p0_reg = 0x0f ;
    gtia.graph_p1_reg = 0x10 ;
    gtia.graph_p2_reg = 0x11 ;
    gtia.graph_p3_reg = 0x12 ;
    gtia.graph_m_reg = 0x13 ;
    gtia.color_pf0_reg = 0x14 ; /* make sure first bit zero for these */
    gtia.color_pf1_reg = 0x16 ;
    gtia.color_pf2_reg = 0x18 ;
    gtia.color_pf3_reg = 0x1a ;
    gtia.color_pm0_reg = 0x1c ;
    gtia.color_pm1_reg = 0x1e ;
    gtia.color_pm2_reg = 0x20 ;
    gtia.color_pm3_reg = 0x22 ;
    gtia.color_bk_reg = 0x24 ;

    gtia.m0pf_reg = 0x01 ;
    gtia.m1pf_reg = 0x02 ;
    gtia.m2pf_reg = 0x03 ;
    gtia.m3pf_reg = 0x04 ;
    gtia.p0pf_reg = 0x05 ;
    gtia.p1pf_reg = 0x06 ;
    gtia.p2pf_reg = 0x07 ;
    gtia.p3pf_reg = 0x08 ;
    gtia.m0pl_reg = 0x09 ;
    gtia.m1pl_reg = 0x0a ;
    gtia.m2pl_reg = 0x0b ;
    gtia.m3pl_reg = 0x0c ;
    gtia.p0pl_reg = 0x0d & 0x0e ;
    gtia.p1pl_reg = 0x0e & 0x0d ;
    gtia.p2pl_reg = 0x0f & 0x0b ;
    gtia.p3pl_reg = 0x01 & 0x07 ;

    /* Now write m/p values to set the collision bits right */
    gtia_write_collision ( M0PF, gtia.m0pf_reg );
    gtia_write_collision ( M1PF, gtia.m1pf_reg );
    gtia_write_collision ( M2PF, gtia.m2pf_reg );
    gtia_write_collision ( M3PF, gtia.m3pf_reg );
    gtia_write_collision ( M0PL, gtia.m0pl_reg );
    gtia_write_collision ( M1PL, gtia.m1pl_reg );
    gtia_write_collision ( M2PL, gtia.m2pl_reg );
    gtia_write_collision ( M3PL, gtia.m3pl_reg );
    gtia_write_collision ( P0PF, gtia.p0pf_reg );
    gtia_write_collision ( P1PF, gtia.p1pf_reg );
    gtia_write_collision ( P2PF, gtia.p2pf_reg );
    gtia_write_collision ( P3PF, gtia.p3pf_reg );
    gtia_write_collision ( P0PL, gtia.p0pl_reg );
    gtia_write_collision ( P1PL, gtia.p1pl_reg );
    gtia_write_collision ( P2PL, gtia.p2pl_reg );
    gtia_write_collision ( P3PL, gtia.p3pl_reg );

    gtia.trig[0] = 2 ;
    gtia.trig[1] = 3 ;
    gtia.trig[2] = 1 ;
    gtia.trig[3] = 4;
    gtia.prior_reg  = 0xf1;
    gtia.vdelay_reg = 2;
    gtia.pal_or_ntsc = 1;

    gtia.console_reg = 0xb;
    gtia.console_mask = 0xf;
    gtia.start_pressed = 1;
    gtia.select_pressed = 1;
    gtia.option_pressed = 0;
    gtia.internal_speaker = 1;
    gtia.keypad = 0x2;
    gtia.pot_enable = 0x4;

    gtia.latch_triggers = 0x04;
    gtia.player_dma = 0x02;
    gtia.missle_dma = 0x01;

    pokey.w_reg[AUDF1]  = 0x10;
    pokey.w_reg[AUDF2]  = 0x11;
    pokey.w_reg[AUDF3]  = 0x12;
    pokey.w_reg[AUDF4]  = 0x13;
    pokey.w_reg[AUDC1]  = 0x14;
    pokey.w_reg[AUDC2]  = 0x1f;
    pokey.w_reg[AUDC3]  = 0x15;
    pokey.w_reg[AUDC4]  = 0x16;
    pokey.w_reg[AUDCTL]  = 0x17;
    pokey.w_reg[STIMER]  = 0x18;
    pokey.sk_status  = 0x19;
    pokey.sk_ctl  = 0x1a;
    pokey.serial_out  = 0x1b;
    pokey.serial_in  = 0x1c;
    pokey.irq_status  = 0x1d;
    pokey.irq_enable  = 0x1e;
    pokey.kbcode  = 0x1f;
    pokey.r_reg[POT0]  = 0x21;
    pokey.r_reg[POT1]  = 0x22;
    pokey.r_reg[POT2]  = 0x23;
    pokey.r_reg[POT3]  = 0x24;
    pokey.r_reg[POT4]  = 0x25;
    pokey.r_reg[POT5]  = 0x26;
    pokey.r_reg[POT6]  = 0x27;
    pokey.r_reg[POT7]  = 0x28;
    pokey.r_reg[ALLPOT]  = 0x29;
    pokey.current_key_code  = 0x2a;
    pokey.pot_scanline  = 0x2b;
    pokey.serial_mode_status  = 0x2c;
    pokey.serial_hw_status  = 0x2d;
    pokey.serial_irq_time  = 0x2e;
    pokey.serial_out_time  = 0x2f;
    pokey.random_last_clock = 0x30;
    pokey.timer_irq_next_clock = 0x151;
    pokey.timer_irq_next_clock_valid = 1;
    for ( i = 0; i < 3; ++i ) {
        pokey.timer_irq[i].armed = i & 1;
        pokey.timer_irq[i].reload_cycles = 0x140 + i;
        pokey.timer_irq[i].next_fire_clock = 0x150 + i;
    }
    pokey_cpu_clock = 123456;

    pokeyChip[0].clockPolyStart[POLY4_SEL]  = 0x31;
    pokeyChip[0].clockPolyStart[POLY17_SEL]  = 0x32;
    pokeyChip[0].clockPolyStart[POLY9_SEL]  = 0x33;
    pokeyChip[0].clockPoly5Start  = 0x34;
    pokeyChip[0].clock114Time  = 0x35;
    pokeyChip[0].clock28Time  = 0x36;
    pokeyChip[0].volChanBits  = 0x37;

    for ( i = 0; i < 4; ++i ) {
        pokeyChip[0].chan[i].toggle  = 0x38+i;
        pokeyChip[0].chan[i].toggleClk  = 0x39+i;
        pokeyChip[0].chan[i].lastEventTime  = 0x3a+i;
        pokeyChip[0].chan[i].ffMainOut  = 0x3b+i;
        pokeyChip[0].chan[i].ffPoly5Out  = 0x3c+i;
        pokeyChip[0].chan[i].ffFilterOut  = 0x3d+i;
    }
 
    /* PIA */
    pia.port_value[PORTA] = 0x34;
    pia.port_value[PORTB] = 0x35;
    pia.input[PORTA] = 0x47;
    pia.input[PORTB] = 0x48;
    pia.direction[PORTA] = 0x21;
    pia.direction[PORTB] = 0x22;
    pia.port_control[PACTL&0x1] = 0x31;
    pia.port_control[PBCTL&0x1] = 0x32;
    mem_assign_portb_ops ( &cpu6502, 0x33 );

    /* SIO */
    sio.current_device = &sio.drives[4].dev;
    sio.motor_on = 1;
    sio.motor_on_time = 2;
    sio.cmd_index = 3;
    for ( i = 0; i < 6; ++i )
        sio.cmd_frame[i] = 0x7;

    /* Disk Devices */
    for ( i = 0; i < MAX_SIO_DEVICES; ++i ) {

        if ( i == 2 )
            continue;

        sio.drives[i].write_protect = 4;
        sio.drives[i].sector_size = 230;
        sio.drives[i].dsk_type = 1;

        sprintf ( sio.drives[i].dev.filename, "testfile%d", i );
        sio.drives[i].dev.buffer_size = 200;
        sio.drives[i].dev.num_blocks = 0x13;
        sio.drives[i].dev.current_block = 0x14;
        sio.drives[i].dev.current_byte = 0x15;
        sio.drives[i].dev.expected_bytes = 0x16;
        sio.drives[i].dev.header_size = 0x17;
        sio.drives[i].dev.baud = 0x18;
        sio.drives[i].dev.action_time = 0x19;
        sio.drives[i].dev.status = 0x1a;
        sio.drives[i].dev.type = 0x1b;
        sio.drives[i].dev.f_buffer = malloc(sio.drives[i].dev.buffer_size*sizeof(KBYTE));
        for ( j = 0; j < sio.drives[i].dev.buffer_size; ++j )
            sio.drives[i].dev.f_buffer[j] = rand() & 0xff;
        sio.drives[i].dev.current_data = &sio.drives[i].dev.f_buffer[10 + i];
        for ( j = 0; j < 16; ++j )
            sio.drives[i].dev.header[j] = 0xa;
        for ( j = 0; j < 260; ++j )
            sio.drives[i].dev.data_frame[j] = 0xb;

        util_alloc_string ( &sio.drives[i].media_info.title, "TestFloppy" );
        util_alloc_string ( &sio.drives[i].media_info.file, "/usr/share/games/testflop.bin" );
        sio.drives[i].media_info.media_type = CART_FLOPPY;
        sio.drives[i].media_info.mapping = 11;
        sio.drives[i].media_info.image_size = 201877;
        sio.drives[i].media_info.write_protect = 4;
        sio.drives[i].media_info.crc = 0x081a3456;
        sio.drives[i].media_info.collections = 0xcb2e9678;
        util_alloc_string ( &sio.drives[i].media_info.md5, "82745678523456781234567812345678" );
        util_alloc_string ( &sio.drives[i].media_info.sha1, 
        "7294567802345678123456781234567887654321" );
    }

    /* Cassette */
    sio.cassette.record_pressed = 5;
    sio.cassette.motor_gap_delay = 6;
    strcpy ( sio.cassette.description, "mycass" );

    strcpy ( sio.cassette.dev.filename, "testcass" );
    sio.cassette.dev.buffer_size = 200;
    sio.cassette.dev.num_blocks = 0x13;
    sio.cassette.dev.current_block = 0x14;
    sio.cassette.dev.current_byte = 0x15;
    sio.cassette.dev.expected_bytes = 0x16;
    sio.cassette.dev.header_size = 0x17;
    sio.cassette.dev.baud = 0x18;
    sio.cassette.dev.action_time = 0x19;
    sio.cassette.dev.status = 0x1a;
    sio.cassette.dev.type = 0x1b;
    sio.cassette.dev.f_buffer = malloc(sio.cassette.dev.buffer_size*sizeof(KBYTE));
    for ( j = 0; j < sio.cassette.dev.buffer_size; ++j )
        sio.cassette.dev.f_buffer[j] = rand() & 0xff;
    sio.cassette.dev.current_data = &sio.cassette.dev.f_buffer[40];
    for ( j = 0; j < 16; ++j )
        sio.cassette.dev.header[j] = 0xd;
    for ( j = 0; j < 260; ++j )
        sio.cassette.dev.data_frame[j] = 0xbe;
    for ( i = 0; i < MAX_CAS_BLOCKS; ++i ) {
        sio.cassette.blk[i].data_ptr = &sio.cassette.dev.f_buffer[i/12];
        sio.cassette.blk[i].size = i + 2;
        sio.cassette.blk[i].mark_tone_size = i + 4;
    }

    util_alloc_string ( &sio.cassette.media_info.title, "TestCass" );
    util_alloc_string ( &sio.cassette.media_info.file, "/usr/share/games/testcass.bin" );
    sio.cassette.media_info.media_type = CART_CASSETTE;
    sio.cassette.media_info.mapping = 11;
    sio.cassette.media_info.image_size = 101877;
    sio.cassette.media_info.write_protect = 1;
    sio.cassette.media_info.crc = 0x981a3456;
    sio.cassette.media_info.collections = 0xab209678;
    util_alloc_string ( &sio.cassette.media_info.md5, "82745678523456781234567812345678" );
    util_alloc_string ( &sio.cassette.media_info.sha1, 
        "7294567802345678123456781234567887654321" );
    /*
     * Lets save off our values for checking later
     */
    origAtari.cpu = &cpuOrig;
    origAtari.antic = &anticOrig;
    origAtari.gtia = &gtiaOrig;
    origAtari.pokey = &pokeyOrig;
    origAtari.pia = &piaOrig;
    origAtari.sio = &sioOrig;
    origAtari.cart = &cartOrig;
    origAtari.cart_right = &cart_rightOrig;

    cpuOrig = cpu6502;
    anticOrig = antic;
    gtiaOrig = gtia;
    pokeyOrig = pokey;
    piaOrig = pia;
    sioOrig = sio;
    cartOrig = cart;
    cart_rightOrig = cart_right;
    for ( i = 0; i < MAXPOKEYS; ++i )
        pokeyChipOrig[i] = pokeyChip[i];
    pokey_cpu_clockOrig = pokey_cpu_clock;

    origAtari.frame_number = myAtari.frame_number;
    origAtari.machine_type = myAtari.machine_type;
    origAtari.frames_per_sec = myAtari.frames_per_sec;
    origAtari.scanlines = myAtari.scanlines;
    origAtari.debug_mode = myAtari.debug_mode;
    origAtari.throttle = myAtari.throttle;
    origAtari.ram_size = myAtari.ram_size;
    origAtari.basic_enable = myAtari.basic_enable;
    origAtari.option_key_pressed = myAtari.option_key_pressed;
    origAtari.start_key_pressed = myAtari.start_key_pressed;

    portbOrig = portb;
    mem_imageOrig = mem_image;
    memcpy ( bankOrig, bank, sizeof(KBYTE) * 0x20000 );
    memcpy ( memoryOrig, memory, sizeof(KBYTE) * 0x10000 );
    for ( i = 0; i < 0x10000; ++i ) {
        xe_bankOrig[i] = read_xe_bank_state(i);
    }

    for ( i = 0; i < 0x4000; ++i ) {
        osOrig[i] = read_os_rom(i);
    }

    for ( i = 0; i < 0x2000; ++i ) {
        basicOrig[i] = read_basic_rom(i);
    }

    /*
     * Save the states and check for existance
     */
    int exists[9] = {0};
    char result_string[100];

    main_result += result = states_save_quick_state ( 2, &myAtari );
    output_result ( result, "Quick State Save", "2", "Unable to save" );

    main_result += result = states_save_quick_state ( 7, &myAtari );
    output_result ( result, "Quick State Save", "7", "Unable to save" );
    
    states_get_quick_state_existance ( exists );
    result = exists[1] ? 0 : 1;
    result += exists[6] ? 0 : 1;
    for ( i = 0; i < 9; ++i ) {
        if ( i == 1  || i == 6 ) 
            result += exists[i] ? 0 : 1;
        else
            result += exists[i] ? 1 : 0;
        if ( exists[i] )
            sprintf ( result_string, "%d,", i+1 ); 
    }
    main_result += result;
    output_result ( result, "Quick State 2,7 Exists", "2 and 7", "" );

    /* 
     * Clear out structs before recalling
     */
    memset ( &cpu6502, 0, sizeof(t_cpu));
    memset ( &antic, 0, sizeof(t_antic));
    memset ( &gtia, 0, sizeof(t_gtia));
    memset ( &pokey, 0, sizeof(t_pokey));
    memset ( &pia, 0, sizeof(t_pia));
    memset ( &sio, 0, sizeof(t_sio_interface));
    memset ( &cart, 0, sizeof(t_cart));
    memset ( &cart_right, 0, sizeof(t_cart));
    memset ( &myAtari, 0, sizeof(t_atari));

    myAtari.cpu = &cpu6502;
    myAtari.antic = &antic;
    myAtari.gtia = &gtia;
    myAtari.pokey = &pokey;
    myAtari.pia = &pia;
    myAtari.sio = &sio;
    myAtari.cart = &cart;
    myAtari.cart_right = &cart_right;

    for ( i = 0; i < MAXPOKEYS; ++i )
        memset ( &pokeyChip[i], 0, sizeof(ps_chip));

    memset ( memory, 0, 0x10000 * sizeof(KBYTE) );
    memset ( bank, 0, 0x20000 * sizeof(KBYTE) );
    for ( i = 0; i < 0x10000; ++i ) {
        write_xe_bank_state(i, 0);
    }
    for ( i = 0; i < 0x4000; ++i ) {
        write_os_rom(i, 0);
    }
    for ( i = 0; i < 0x2000; ++i ) {
        write_basic_rom(i, 0);
    }
    pokey_cpu_clock = 0;
    mem_set_state_values( 0, 0, 0 );
    sio_init();

    unsigned int color_rgb[256];
    gtia_set_screen ( 0, color_rgb, 320, 8, 0 );

    /* Recall the state */
    main_result += result = states_recall_quick_state ( 2, &myAtari );
    output_result ( result, "Recall Quick State", "2", "Unable to recall quick state" );
    bank = mem_get_bank_image();

    /* 
     * Now we check for correct recall of the state 
     */
    main_result += result = (myAtari.frame_number != origAtari.frame_number) ? 1 : 0;
    output_result ( result, "Console", "Frame Number", "Incorrect" );
    main_result += result = (myAtari.machine_type != origAtari.machine_type) ? 1 : 0;
    output_result ( result, "Console", "Machine Type", "Incorrect" );
    main_result += result = (myAtari.ram_size != origAtari.ram_size) ? 1 : 0;
    output_result ( result, "Console", "Ram Size", "Incorrect" );
    main_result += result = (myAtari.basic_enable != origAtari.basic_enable) ? 1 : 0;
    output_result ( result, "Console", "Basic Enable", "Incorrect" );
    main_result += result = (myAtari.option_key_pressed != origAtari.option_key_pressed) ? 1 : 0;
    output_result ( result, "Console", "Option Key Pressed", "Incorrect" );
    main_result += result = (myAtari.start_key_pressed != origAtari.start_key_pressed) ? 1 : 0;
    output_result ( result, "Console", "Start Key Pressed", "Incorrect" );

    main_result += result = (cart.loaded != cartOrig.loaded) ? 1: 0;
    output_result ( result, "Console", "Cart Loaded", "Incorrect" );

    main_result += result = string_compare(cart.media_info.title,cartOrig.media_info.title) ? 1: 0;
    output_result ( result, "Console", "Cart Media Title", "Incorrect" );
    main_result += result = string_compare(cart.media_info.file,cartOrig.media_info.file) ? 1: 0;
    output_result ( result, "Console", "Cart Media File", "Incorrect" );
    main_result += result = string_compare(cart.media_info.md5,cartOrig.media_info.md5) ? 1: 0;
    output_result ( result, "Console", "Cart Media MD5", "Incorrect" );
    main_result += result = string_compare(cart.media_info.sha1,cartOrig.media_info.sha1) ? 1: 0;
    output_result ( result, "Console", "Cart Media SHA1", "Incorrect" );
    main_result += result = (cart.media_info.media_type != cartOrig.media_info.media_type) ? 1: 0;
    output_result ( result, "Console", "Cart Media Type", "Incorrect" );
    main_result += result = (cart.media_info.mapping != cartOrig.media_info.mapping) ? 1: 0;
    output_result ( result, "Console", "Cart Media Mapping", "Incorrect" );
    main_result += result = (cart.media_info.image_size != cartOrig.media_info.image_size) ? 1: 0;
    output_result ( result, "Console", "Cart Media Image Size", "Incorrect" );
    main_result += result = (cart.media_info.write_protect != cartOrig.media_info.write_protect) ? 1: 0;
    output_result ( result, "Console", "Cart Media Write Protect", "Incorrect" );
    main_result += result = (cart.media_info.crc != cartOrig.media_info.crc) ? 1: 0;
    output_result ( result, "Console", "Cart Media CRC", "Incorrect" );
    main_result += result = (cart.media_info.collections != cartOrig.media_info.collections) ? 1: 0;
    output_result ( result, "Console", "Cart Media Collections", "Incorrect" );

    main_result += result = string_compare(cart_right.media_info.title,cart_rightOrig.media_info.title) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Title", "Incorrect" );
    main_result += result = string_compare(cart_right.media_info.file,cart_rightOrig.media_info.file) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media File", "Incorrect" );
    main_result += result = string_compare(cart_right.media_info.md5,cart_rightOrig.media_info.md5) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media MD5", "Incorrect" );
    main_result += result = string_compare(cart_right.media_info.sha1,cart_rightOrig.media_info.sha1) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media SHA1", "Incorrect" );
    main_result += result = (cart_right.media_info.media_type != cart_rightOrig.media_info.media_type) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Type", "Incorrect" );
    main_result += result = (cart_right.media_info.mapping != cart_rightOrig.media_info.mapping) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Mapping", "Incorrect" );
    main_result += result = (cart_right.media_info.image_size != cart_rightOrig.media_info.image_size) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Image Size", "Incorrect" );
    main_result += result = (cart_right.media_info.write_protect != cart_rightOrig.media_info.write_protect) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Write Protect", "Incorrect" );
    main_result += result = (cart_right.media_info.crc != cart_rightOrig.media_info.crc) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media CRC", "Incorrect" );
    main_result += result = (cart_right.media_info.collections != cart_rightOrig.media_info.collections) ? 1: 0;
    output_result ( result, "Console", "Cart Right Media Collections", "Incorrect" );

    main_result += result = memcmp(memory, memoryOrig, sizeof(KBYTE)*0x10000) ? 1: 0;
    output_result ( result, "Memory", "Memory", "Incorrect" );
    main_result += result = memcmp(bank, bankOrig, sizeof(KBYTE)*0x20000) ? 1: 0;
    output_result ( result, "Memory", "Bank", "Incorrect" );

    if ( origAtari.machine_type == MACHINE_TYPE_XL ) {
        int mem_result = 0;
        for ( i = 0; i < 0x10000; ++i ) {
            if ( xe_bankOrig[i] != read_xe_bank_state(i) )
                mem_result++;
        }
        output_result ( mem_result, "Memory", "XE Bank", "Incorrect" );
        main_result += mem_result;

        mem_result = 0;
        for ( i = 0; i < 0x4000; ++i ) {
            if ( osOrig[i] != read_os_rom(i) )
                mem_result++;
        }
        output_result ( mem_result, "Memory", "OS Rom", "Incorrect" );
        main_result += mem_result;
    
        mem_result = 0;
        for ( i = 0; i < 0x2000; ++i ) {
            if ( basicOrig[i] != read_basic_rom(i) )
                mem_result++;
        }
        output_result ( mem_result, "Memory", "BASIC Rom", "Incorrect" );
        main_result += mem_result;
    }

    main_result += result = (cpu6502.i_req != cpuOrig.i_req) ? 1: 0;
    output_result ( result, "Cpu", "I Req", "Incorrect" );
    main_result += result = (cpu6502.irq_assert != cpuOrig.irq_assert) ? 1: 0;
    output_result ( result, "Cpu", "IRQ Assert", "Incorrect" );
    main_result += result = (cpu6502.i_cnt != cpuOrig.i_cnt) ? 1: 0;
    output_result ( result, "Cpu", "I Cnt", "Incorrect" );
    main_result += result = (cpu6502.status != cpuOrig.status) ? 1: 0;
    output_result ( result, "Cpu", "Status", "Incorrect" );
    main_result += result = (cpu6502.pc != cpuOrig.pc) ? 1: 0;
    output_result ( result, "Cpu", "PC", "Incorrect" );
    main_result += result = (cpu6502.a_reg != cpuOrig.a_reg) ? 1: 0;
    output_result ( result, "Cpu", "A Reg", "Incorrect" );
    main_result += result = (cpu6502.x_reg != cpuOrig.x_reg) ? 1: 0;
    output_result ( result, "Cpu", "X Reg", "Incorrect" );
    main_result += result = (cpu6502.y_reg != cpuOrig.y_reg) ? 1: 0;
    output_result ( result, "Cpu", "Y Reg", "Incorrect" );
    main_result += result = memcmp(cpu6502.stack, cpuOrig.stack, sizeof(KWORD)*0x100) ? 1: 0;
    output_result ( result, "Cpu", "Stack", "Incorrect" );
    main_result += result = (cpu6502.stack_ptr != cpuOrig.stack_ptr) ? 1: 0;
    output_result ( result, "Cpu", "Stack Ptr", "Incorrect" );

    main_result += result = (antic.dmactl_reg != anticOrig.dmactl_reg) ? 1 : 0;
    output_result ( result, "Antic", "DMA Ctl", "Incorrect" );
    main_result += result = (antic.chactl_reg != anticOrig.chactl_reg) ? 1 : 0;
    output_result ( result, "Antic", "Char Ctl", "Incorrect" );
    main_result += result = (antic.dlistl_reg != anticOrig.dlistl_reg) ? 1 : 0;
    output_result ( result, "Antic", "DList Low", "Incorrect" );
    main_result += result = (antic.dlisth_reg != anticOrig.dlisth_reg) ? 1 : 0;
    output_result ( result, "Antic", "DList Hi", "Incorrect" );
    main_result += result = (antic.hscroll_reg_value != anticOrig.hscroll_reg_value) ? 1 : 0;
    output_result ( result, "Antic", "HSCROLL", "Incorrect" );
    main_result += result = (antic.vscroll_reg_value != anticOrig.vscroll_reg_value) ? 1 : 0;
    output_result ( result, "Antic", "VSCROLL", "Incorrect" );
    main_result += result = (antic.pm_base_address != anticOrig.pm_base_address) ? 1 : 0;
    output_result ( result, "Antic", "PM Base", "Incorrect" );
    main_result += result = (antic.ch_base_address != anticOrig.ch_base_address) ? 1 : 0;
    output_result ( result, "Antic", "CH Base", "Incorrect" );
    main_result += result = (antic.nmi_status_reg != anticOrig.nmi_status_reg) ? 1 : 0;
    output_result ( result, "Antic", "NMI Status", "Incorrect" );
    main_result += result = (antic.nmi_dli_en != anticOrig.nmi_dli_en) ? 1 : 0;
    output_result ( result, "Antic", "NMI DLI En", "Incorrect" );
    main_result += result = (antic.nmi_vbi_en != anticOrig.nmi_vbi_en) ? 1 : 0;
    output_result ( result, "Antic", "NMI VBI En", "Incorrect" );
    main_result += result = (antic.nmi_reset_en != anticOrig.nmi_reset_en) ? 1 : 0;
    output_result ( result, "Antic", "NMI Reset En", "Incorrect" );
    main_result += result = (antic.vcount != anticOrig.vcount) ? 1 : 0;
    output_result ( result, "Antic", "Vcount", "Incorrect" );
    main_result += result = (antic.msc != anticOrig.msc) ? 1 : 0;
    output_result ( result, "Antic", "MSC", "Incorrect" );

    main_result += result = (gtia.hpos_p0_reg != gtiaOrig.hpos_p0_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos P0", "Incorrect" );
    main_result += result = (gtia.hpos_p1_reg != gtiaOrig.hpos_p1_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos P1", "Incorrect" );
    main_result += result = (gtia.hpos_p2_reg != gtiaOrig.hpos_p2_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos P2", "Incorrect" );
    main_result += result = (gtia.hpos_p3_reg != gtiaOrig.hpos_p3_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos P3", "Incorrect" );
    main_result += result = (gtia.hpos_m0_reg != gtiaOrig.hpos_m0_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos M0", "Incorrect" );
    main_result += result = (gtia.hpos_m1_reg != gtiaOrig.hpos_m1_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos M1", "Incorrect" );
    main_result += result = (gtia.hpos_m2_reg != gtiaOrig.hpos_m2_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos M2", "Incorrect" );
    main_result += result = (gtia.hpos_m3_reg != gtiaOrig.hpos_m3_reg) ? 1: 0;
    output_result ( result, "GTIA", "Hpos M3", "Incorrect" );
    main_result += result = (gtia.w_reg[SIZEP0] != gtiaOrig.w_reg[SIZEP0]) ? 1: 0;
    output_result ( result, "GTIA", "Size P0", "Incorrect" );
    main_result += result = (gtia.w_reg[SIZEP1] != gtiaOrig.w_reg[SIZEP1] ) ? 1 : 0;
    output_result ( result, "GTIA", "Size P1", "Incorrect" );
    main_result += result = (gtia.w_reg[SIZEP2] != gtiaOrig.w_reg[SIZEP2] ) ? 1 : 0;
    output_result ( result, "GTIA", "Size P2", "Incorrect" );
    main_result += result = (gtia.w_reg[SIZEP3] != gtiaOrig.w_reg[SIZEP3] ) ? 1 : 0;
    output_result ( result, "GTIA", "Size P3", "Incorrect" );
    main_result += result = (gtia.w_reg[SIZEM] != gtiaOrig.w_reg[SIZEM] ) ? 1 : 0;
    output_result ( result, "GTIA", "Size M", "Incorrect" );
    main_result += result = (gtia.graph_p0_reg != gtiaOrig.graph_p0_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Graph P0", "Incorrect" );
    main_result += result = (gtia.graph_p1_reg != gtiaOrig.graph_p1_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Graph P1", "Incorrect" );
    main_result += result = (gtia.graph_p2_reg != gtiaOrig.graph_p2_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Graph P2", "Incorrect" );
    main_result += result = (gtia.graph_p3_reg != gtiaOrig.graph_p3_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Graph P3", "Incorrect" );
    main_result += result = (gtia.graph_m_reg != gtiaOrig.graph_m_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Graph M", "Incorrect" );
    main_result += result = (gtia.color_pf0_reg != gtiaOrig.color_pf0_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PF0", "Incorrect" );
    main_result += result = (gtia.color_pf1_reg != gtiaOrig.color_pf1_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PF1", "Incorrect" );
    main_result += result = (gtia.color_pf2_reg != gtiaOrig.color_pf2_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PF2", "Incorrect" );
    main_result += result = (gtia.color_pf3_reg != gtiaOrig.color_pf3_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PF3", "Incorrect" );
    main_result += result = (gtia.color_pm0_reg != gtiaOrig.color_pm0_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PM0", "Incorrect" );
    main_result += result = (gtia.color_pm1_reg != gtiaOrig.color_pm1_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PM1", "Incorrect" );
    main_result += result = (gtia.color_pm2_reg != gtiaOrig.color_pm2_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PM2", "Incorrect" );
    main_result += result = (gtia.color_pm3_reg != gtiaOrig.color_pm3_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color PM3", "Incorrect" );
    main_result += result = (gtia.color_bk_reg != gtiaOrig.color_bk_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Color BK", "Incorrect" );
    main_result += result = (gtia.m0pf_reg != gtiaOrig.m0pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M0PF", "Incorrect" );
    main_result += result = (gtia.m1pf_reg != gtiaOrig.m1pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M1PF", "Incorrect" );
    main_result += result = (gtia.m2pf_reg != gtiaOrig.m2pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M2PF", "Incorrect" );
    main_result += result = (gtia.m3pf_reg != gtiaOrig.m3pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M3PF", "Incorrect" );
    main_result += result = (gtia.p0pf_reg != gtiaOrig.p0pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P0PF", "Incorrect" );
    main_result += result = (gtia.p1pf_reg != gtiaOrig.p1pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P1PF", "Incorrect" );
    main_result += result = (gtia.p2pf_reg != gtiaOrig.p2pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P2PF", "Incorrect" );
    main_result += result = (gtia.p3pf_reg != gtiaOrig.p3pf_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P3PF", "Incorrect" );
    main_result += result = (gtia.m0pl_reg != gtiaOrig.m0pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M0PL", "Incorrect" );
    main_result += result = (gtia.m1pl_reg != gtiaOrig.m1pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M1PL", "Incorrect" );
    main_result += result = (gtia.m2pl_reg != gtiaOrig.m2pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M2PL", "Incorrect" );
    main_result += result = (gtia.m3pl_reg != gtiaOrig.m3pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "M3PL", "Incorrect" );
    main_result += result = (gtia.p0pl_reg != gtiaOrig.p0pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P0PL", "Incorrect" );
    main_result += result = (gtia.p1pl_reg != gtiaOrig.p1pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P1PL", "Incorrect" );
    main_result += result = (gtia.p2pl_reg != gtiaOrig.p2pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P2PL", "Incorrect" );
    main_result += result = (gtia.p3pl_reg != gtiaOrig.p3pl_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "P3PL", "Incorrect" );
    main_result += result = (gtia.trig[0] != gtiaOrig.trig[0] ) ? 1 : 0;
    output_result ( result, "GTIA", "TRIG0", "Incorrect" );
    main_result += result = (gtia.trig[1] != gtiaOrig.trig[1] ) ? 1 : 0;
    output_result ( result, "GTIA", "TRIG1", "Incorrect" );
    main_result += result = (gtia.trig[2] != gtiaOrig.trig[2] ) ? 1 : 0;
    output_result ( result, "GTIA", "TRIG2", "Incorrect" );
    if ( myAtari.machine_type == MACHINE_TYPE_XL ) /* can't really test this right */
        main_result += result = (gtia.trig[3] != 0 ) ? 1 : 0;
    else
        main_result += result = (gtia.trig[3] != gtiaOrig.trig[3] ) ? 1 : 0;
    output_result ( result, "GTIA", "TRIG3", "Incorrect" );
    main_result += result = (gtia.prior_reg  != gtiaOrig.prior_reg  ) ? 1 : 0;
    output_result ( result, "GTIA", "Prior Reg", "Incorrect" );
    main_result += result = (gtia.vdelay_reg != gtiaOrig.vdelay_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "VDELAY Reg", "Incorrect" );
    main_result += result = (gtia.pal_or_ntsc != gtiaOrig.pal_or_ntsc ) ? 1 : 0;
    output_result ( result, "GTIA", "PAL/NTSC", "Incorrect" );
    main_result += result = (gtia.latch_triggers != gtiaOrig.latch_triggers ) ? 1 : 0;
    output_result ( result, "GTIA", "Latch Triggers", "Incorrect" );
    main_result += result = (gtia.player_dma != gtiaOrig.player_dma ) ? 1 : 0;
    output_result ( result, "GTIA", "Player DMA", "Incorrect" );
    main_result += result = (gtia.missle_dma != gtiaOrig.missle_dma ) ? 1 : 0;
    output_result ( result, "GTIA", "Missle DMA", "Incorrect" );
    main_result += result = (gtia.console_reg != gtiaOrig.console_reg ) ? 1 : 0;
    output_result ( result, "GTIA", "Console Reg", "Incorrect" );
    main_result += result = (gtia.console_mask != gtiaOrig.console_mask ) ? 1 : 0;
    output_result ( result, "GTIA", "Console Mask", "Incorrect" );
    main_result += result = (gtia.start_pressed != gtiaOrig.start_pressed ) ? 1 : 0;
    output_result ( result, "GTIA", "Start Pressed", "Incorrect" );
    main_result += result = (gtia.select_pressed != gtiaOrig.select_pressed ) ? 1 : 0;
    output_result ( result, "GTIA", "Select Pressed", "Incorrect" );
    main_result += result = (gtia.option_pressed != gtiaOrig.option_pressed ) ? 1 : 0;
    output_result ( result, "GTIA", "Option Pressed", "Incorrect" );
    main_result += result = (gtia.internal_speaker != gtiaOrig.internal_speaker ) ? 1 : 0;
    output_result ( result, "GTIA", "Internal Speaker", "Incorrect" );
    main_result += result = (gtia.keypad != gtiaOrig.keypad ) ? 1 : 0;
    output_result ( result, "GTIA", "Keypad", "Incorrect" );
    main_result += result = (gtia.pot_enable != gtiaOrig.pot_enable ) ? 1 : 0;
    output_result ( result, "GTIA", "Pot Enable", "Incorrect" );

    main_result += result = (pokey.w_reg[AUDF1] != pokeyOrig.w_reg[AUDF1]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDF1", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDF2] != pokeyOrig.w_reg[AUDF2]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDF2", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDF3] != pokeyOrig.w_reg[AUDF3]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDF3", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDF4] != pokeyOrig.w_reg[AUDF4]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDF4", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDC1] != pokeyOrig.w_reg[AUDC1]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDC1", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDC2] != pokeyOrig.w_reg[AUDC2]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDC2", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDC3] != pokeyOrig.w_reg[AUDC3]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDC3", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDC4] != pokeyOrig.w_reg[AUDC4]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDC4", "Incorrect" );
    main_result += result = (pokey.w_reg[AUDCTL] != pokeyOrig.w_reg[AUDCTL]) ? 1 : 0;
    output_result ( result, "Pokey", "AUDCTL", "Incorrect" );
    main_result += result = (pokey.w_reg[STIMER] != pokeyOrig.w_reg[STIMER]) ? 1 : 0;
    output_result ( result, "Pokey", "STIMER", "Incorrect" );
    main_result += result = (pokey.sk_status != pokeyOrig.sk_status) ? 1 : 0;
    output_result ( result, "Pokey", "Sk Status", "Incorrect" );
    main_result += result = (pokey.sk_ctl != pokeyOrig.sk_ctl) ? 1 : 0;
    output_result ( result, "Pokey", "Sk Ctl", "Incorrect" );
    main_result += result = (pokey.serial_out != pokeyOrig.serial_out) ? 1 : 0;
    output_result ( result, "Pokey", "Serial Out", "Incorrect" );
    main_result += result = (pokey.serial_in != pokeyOrig.serial_in) ? 1 : 0;
    output_result ( result, "Pokey", "Serial In", "Incorrect" );
    main_result += result = (pokey.irq_status != pokeyOrig.irq_status) ? 1 : 0;
    output_result ( result, "Pokey", "IRQ Status", "Incorrect" );
    main_result += result = (pokey.irq_enable != pokeyOrig.irq_enable) ? 1 : 0;
    output_result ( result, "Pokey", "IRQ Enable", "Incorrect" );
    main_result += result = (pokey.kbcode != pokeyOrig.kbcode) ? 1 : 0;
    output_result ( result, "Pokey", "KB Code", "Incorrect" );
    main_result += result = (pokey.r_reg[POT0] != pokeyOrig.r_reg[POT0]) ? 1 : 0;
    output_result ( result, "Pokey", "POT0", "Incorrect" );
    main_result += result = (pokey.r_reg[POT1] != pokeyOrig.r_reg[POT1]) ? 1 : 0;
    output_result ( result, "Pokey", "POT1", "Incorrect" );
    main_result += result = (pokey.r_reg[POT2] != pokeyOrig.r_reg[POT2]) ? 1 : 0;
    output_result ( result, "Pokey", "POT2", "Incorrect" );
    main_result += result = (pokey.r_reg[POT3] != pokeyOrig.r_reg[POT3]) ? 1 : 0;
    output_result ( result, "Pokey", "POT3", "Incorrect" );
    main_result += result = (pokey.r_reg[POT4] != pokeyOrig.r_reg[POT4]) ? 1 : 0;
    output_result ( result, "Pokey", "POT4", "Incorrect" );
    main_result += result = (pokey.r_reg[POT5] != pokeyOrig.r_reg[POT5]) ? 1 : 0;
    output_result ( result, "Pokey", "POT5", "Incorrect" );
    main_result += result = (pokey.r_reg[POT6] != pokeyOrig.r_reg[POT6]) ? 1 : 0;
    output_result ( result, "Pokey", "POT6", "Incorrect" );
    main_result += result = (pokey.r_reg[POT7] != pokeyOrig.r_reg[POT7]) ? 1 : 0;
    output_result ( result, "Pokey", "POT7", "Incorrect" );
    main_result += result = (pokey.r_reg[ALLPOT] != pokeyOrig.r_reg[ALLPOT]) ? 1 : 0;
    output_result ( result, "Pokey", "ALLPOT", "Incorrect" );
    main_result += result = (pokey.current_key_code != pokeyOrig.current_key_code) ? 1 : 0;
    output_result ( result, "Pokey", "Current Keycode", "Incorrect" );
    main_result += result = (pokey.pot_scanline != pokeyOrig.pot_scanline) ? 1 : 0;
    output_result ( result, "Pokey", "Pot Scanline", "Incorrect" );
    main_result += result = (pokey.serial_mode_status != pokeyOrig.serial_mode_status) ? 1 : 0;
    output_result ( result, "Pokey", "Serial Mode Status", "Incorrect" );
    main_result += result = (pokey.serial_hw_status != pokeyOrig.serial_hw_status) ? 1 : 0;
    output_result ( result, "Pokey", "Serial HW Status", "Incorrect" );
    main_result += result = (pokey.serial_irq_time != pokeyOrig.serial_irq_time) ? 1 : 0;
    output_result ( result, "Pokey", "Serial IRQ time", "Incorrect" );
    main_result += result = (pokey.serial_out_time != pokeyOrig.serial_out_time) ? 1 : 0;
    output_result ( result, "Pokey", "Serial Out time", "Incorrect" );
    main_result += result = (pokey.random_last_clock != pokeyOrig.random_last_clock) ? 1 : 0;
    output_result ( result, "Pokey", "Random Last Clock", "Incorrect" );
    main_result += result = (pokey.timer_irq_next_clock != pokeyOrig.timer_irq_next_clock) ? 1 : 0;
    output_result ( result, "Pokey", "Timer IRQ Next Clock", "Incorrect" );
    main_result += result = (pokey.timer_irq_next_clock_valid != pokeyOrig.timer_irq_next_clock_valid) ? 1 : 0;
    output_result ( result, "Pokey", "Timer IRQ Next Clock Valid", "Incorrect" );
    for ( i = 0; i < 3; ++i ) {
        sprintf ( err_str, "Timer IRQ %d Armed", i );
        main_result += result = (pokey.timer_irq[i].armed != pokeyOrig.timer_irq[i].armed) ? 1 : 0;
        output_result ( result, "Pokey", err_str, "Incorrect" );
        sprintf ( err_str, "Timer IRQ %d Reload", i );
        main_result += result = (pokey.timer_irq[i].reload_cycles != pokeyOrig.timer_irq[i].reload_cycles) ? 1 : 0;
        output_result ( result, "Pokey", err_str, "Incorrect" );
        sprintf ( err_str, "Timer IRQ %d Next Fire", i );
        main_result += result = (pokey.timer_irq[i].next_fire_clock != pokeyOrig.timer_irq[i].next_fire_clock) ? 1 : 0;
        output_result ( result, "Pokey", err_str, "Incorrect" );
    }
    main_result += result = (pokey_cpu_clock != pokey_cpu_clockOrig) ? 1 : 0;
    output_result ( result, "Pokey", "Pokey CPU Clock", "Incorrect" );

    main_result += result = (pokeyChip[0].clockPolyStart[POLY4_SEL] != 
            pokeyChipOrig[0].clockPolyStart[POLY4_SEL]) ? 1 : 0;
    output_result ( result, "PokeyChip", "Poly4Sel", "Incorrect" );
    main_result += result = (pokeyChip[0].clockPolyStart[POLY17_SEL] != 
            pokeyChipOrig[0].clockPolyStart[POLY17_SEL]) ? 1 : 0;
    output_result ( result, "PokeyChip", "Poly17Sel", "Incorrect" );
    main_result += result = (pokeyChip[0].clockPolyStart[POLY9_SEL] != 
            pokeyChipOrig[0].clockPolyStart[POLY9_SEL]) ? 1 : 0;
    output_result ( result, "PokeyChip", "Poly9Sel", "Incorrect" );
    main_result += result = (pokeyChip[0].clockPoly5Start != 
            pokeyChipOrig[0].clockPoly5Start) ? 1 : 0;
    output_result ( result, "PokeyChip", "Poly5Start", "Incorrect" );
    main_result += result = (pokeyChip[0].clock114Time != 
            pokeyChipOrig[0].clock114Time) ? 1 : 0;
    output_result ( result, "PokeyChip", "clock 114 time", "Incorrect" );
    main_result += result = (pokeyChip[0].clock28Time != 
            pokeyChipOrig[0].clock28Time) ? 1 : 0;
    output_result ( result, "PokeyChip", "clock 28 time", "Incorrect" );
    main_result += result = (pokeyChip[0].volChanBits != 
            pokeyChipOrig[0].volChanBits) ? 1 : 0;
    output_result ( result, "PokeyChip", "Vol Chan Bits", "Incorrect" );

    for ( i = 0; i < 4; ++i ) {
        main_result += result = (pokeyChip[0].chan[i].toggle != 
            pokeyChipOrig[0].chan[i].toggle  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "Toggle", "Incorrect" );
        main_result += result = (pokeyChip[0].chan[i].toggleClk != 
            pokeyChipOrig[0].chan[i].toggleClk  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "Toggle Clock", "Incorrect" );
        main_result += result = (pokeyChip[0].chan[i].lastEventTime != 
            pokeyChipOrig[0].chan[i].lastEventTime  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "Last Event Time", "Incorrect" );
        main_result += result = (pokeyChip[0].chan[i].ffMainOut != 
            pokeyChipOrig[0].chan[i].ffMainOut  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "FF Main Out", "Incorrect" );
        main_result += result = (pokeyChip[0].chan[i].ffPoly5Out != 
            pokeyChipOrig[0].chan[i].ffPoly5Out  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "Poly 5 Out", "Incorrect" );
        main_result += result = (pokeyChip[0].chan[i].ffFilterOut != 
            pokeyChipOrig[0].chan[i].ffFilterOut  ) ? 1 : 0;
        sprintf ( result_string, "PokeyChip Chan%d", i );
        output_result ( result, result_string, "Filter Out", "Incorrect" );
    }

    main_result += result = (pia.port_value[PORTA] != piaOrig.port_value[PORTA]) ? 1 : 0;
    output_result ( result, "Pia", "Port Value A", "Incorrect" );
    main_result += result = (pia.port_value[PORTB] != piaOrig.port_value[PORTB]) ? 1 : 0;
    output_result ( result, "Pia", "Port Value B", "Incorrect" );
    main_result += result = (pia.input[PORTA] != piaOrig.input[PORTA]) ? 1 : 0;
    output_result ( result, "Pia", "Input A", "Incorrect" );
    main_result += result = (pia.input[PORTB] != piaOrig.input[PORTB]) ? 1 : 0;
    output_result ( result, "Pia", "Input B", "Incorrect" );
    main_result += result = (pia.direction[PORTA] != piaOrig.direction[PORTA]) ? 1 : 0;
    output_result ( result, "Pia", "Direction A", "Incorrect" );
    main_result += result = (pia.direction[PORTB] != piaOrig.direction[PORTB]) ? 1 : 0;
    output_result ( result, "Pia", "Direction B", "Incorrect" );
    main_result += result = (pia.port_control[PACTL&0x1] != 
            piaOrig.port_control[PACTL&0x1] ) ? 1 : 0;
    output_result ( result, "Pia", "Port Control A", "Incorrect" );
    main_result += result = (pia.port_control[PBCTL&0x1] != 
            piaOrig.port_control[PBCTL&0x1] ) ? 1 : 0;
    output_result ( result, "Pia", "Port Control B", "Incorrect" );
    main_result += result = (mem_get_portb_value() != 0x33) ? 1 : 0;
    output_result ( result, "Pia", "Port B Mem Ctl", "Incorrect" );

    main_result += result = (sio.current_device != &sio.drives[4].dev) ? 1 : 0;
    output_result ( result, "Sio", "Current Dev", "Incorrect" );
    main_result += result = (sio.motor_on != sioOrig.motor_on) ? 1 : 0;
    output_result ( result, "Sio", "Motor On", "Incorrect" );
    main_result += result = (sio.motor_on_time != sioOrig.motor_on_time) ? 1 : 0;
    output_result ( result, "Sio", "Motor On Time", "Incorrect" );
    main_result += result = (sio.cmd_index != sioOrig.cmd_index) ? 1 : 0;
    output_result ( result, "Sio", "Cmd Index", "Incorrect" );
    main_result += result = memcmp(sio.cmd_frame, sioOrig.cmd_frame, sizeof(KBYTE)*6) ? 1: 0;
    output_result ( result, "Sio", "Cmd Frame", "Incorrect" );

    /* Disk Devices */
    for ( i = 0; i < MAX_SIO_DEVICES; ++i ) {
        sprintf ( result_string, "Sio Drive%d", i );
        main_result += result = (sio.drives[i].write_protect != 
                sioOrig.drives[i].write_protect ) ? 1 : 0;
        output_result ( result, result_string, "Write Protect", "Incorrect" );
        main_result += result = (sio.drives[i].sector_size != 
                sioOrig.drives[i].sector_size ) ? 1 : 0;
        output_result ( result, result_string, "Sector Size", "Incorrect" );
        main_result += result = (sio.drives[i].dsk_type != 
                sioOrig.drives[i].dsk_type ) ? 1 : 0;
        output_result ( result, result_string, "Disk Type", "Incorrect" );

        main_result += result = string_compare(sio.drives[i].dev.filename, 
                sioOrig.drives[i].dev.filename) ? 1 : 0;
        output_result ( result, result_string, "Filename", "Incorrect" );
        main_result += result = (sio.drives[i].dev.buffer_size != 
                sioOrig.drives[i].dev.buffer_size ) ? 1 : 0;
        output_result ( result, result_string, "Buffer Size", "Incorrect" );
        main_result += result = (sio.drives[i].dev.num_blocks != 
                sioOrig.drives[i].dev.num_blocks ) ? 1 : 0;
        output_result ( result, result_string, "Num Blocks", "Incorrect" );
        main_result += result = (sio.drives[i].dev.current_block != 
                sioOrig.drives[i].dev.current_block ) ? 1 : 0;
        output_result ( result, result_string, "Current Block", "Incorrect" );
        main_result += result = (sio.drives[i].dev.current_byte != 
                sioOrig.drives[i].dev.current_byte ) ? 1 : 0;
        output_result ( result, result_string, "Current Byte", "Incorrect" );
        main_result += result = (sio.drives[i].dev.expected_bytes != 
                sioOrig.drives[i].dev.expected_bytes ) ? 1 : 0;
        output_result ( result, result_string, "Expected Bytes", "Incorrect" );
        main_result += result = (sio.drives[i].dev.header_size != 
                sioOrig.drives[i].dev.header_size ) ? 1 : 0;
        output_result ( result, result_string, "Header Size", "Incorrect" );
        main_result += result = (sio.drives[i].dev.baud != 
                sioOrig.drives[i].dev.baud ) ? 1 : 0;
        output_result ( result, result_string, "Dev Baud", "Incorrect" );
        main_result += result = (sio.drives[i].dev.action_time != 
                sioOrig.drives[i].dev.action_time ) ? 1 : 0;
        output_result ( result, result_string, "Action Time", "Incorrect" );
        main_result += result = (sio.drives[i].dev.status != 
                sioOrig.drives[i].dev.status ) ? 1 : 0;
        output_result ( result, result_string, "Status", "Incorrect" );
        main_result += result = (sio.drives[i].dev.type != 
                sioOrig.drives[i].dev.type ) ? 1 : 0;
        output_result ( result, result_string, "Type", "Incorrect" );

        main_result += result = mem_compare(sio.drives[i].dev.f_buffer, 
                sioOrig.drives[i].dev.f_buffer, 
                sizeof(KBYTE) * sio.drives[i].dev.buffer_size) ? 1: 0;
        output_result ( result, result_string, "F Buffer", "Incorrect" );

        main_result += result = (sio.drives[i].dev.f_buffer && (sio.drives[i].dev.current_data != 
                &sio.drives[i].dev.f_buffer[10 + i])) ? 1 : 0; 
        output_result ( result, result_string, "Current Data", "Incorrect" );
        main_result += result = (memcmp ( sio.drives[i].dev.header, 
                    sioOrig.drives[i].dev.header, sizeof(KBYTE) * 16 )) ? 1: 0;
        output_result ( result, result_string, "Header", "Incorrect" );
        main_result += result = memcmp ( sio.drives[i].dev.data_frame, 
                sioOrig.drives[i].dev.data_frame, sizeof(KBYTE) * 260 ) ? 1: 0;
        output_result ( result, result_string, "Data Frame", "Incorrect" );

        main_result += result = string_compare(sio.drives[i].media_info.title,sioOrig.drives[i].media_info.title) ? 1: 0;
        output_result ( result, result_string, "Media Title", "Incorrect" );
        main_result += result = string_compare(sio.drives[i].media_info.file,sioOrig.drives[i].media_info.file) ? 1: 0;
        output_result ( result, result_string, "Media File", "Incorrect" );
        main_result += result = string_compare(sio.drives[i].media_info.md5,sioOrig.drives[i].media_info.md5) ? 1: 0;
        output_result ( result, result_string, "Media MD5", "Incorrect" );
        main_result += result = string_compare(sio.drives[i].media_info.sha1,sioOrig.drives[i].media_info.sha1) ? 1: 0;
        output_result ( result, result_string, "Media SHA1", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.media_type != sioOrig.drives[i].media_info.media_type) ? 1: 0;
        output_result ( result, result_string, "Media Type", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.mapping != sioOrig.drives[i].media_info.mapping) ? 1: 0;
        output_result ( result, result_string, "Media Mapping", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.image_size != sioOrig.drives[i].media_info.image_size) ? 1: 0;
        output_result ( result, result_string, "Media Image Size", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.write_protect != sioOrig.drives[i].media_info.write_protect) ? 1: 0;
        output_result ( result, result_string, "Media Write Protect", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.crc != sioOrig.drives[i].media_info.crc) ? 1: 0;
        output_result ( result, result_string, "Media CRC", "Incorrect" );
        main_result += result = (sio.drives[i].media_info.collections != sioOrig.drives[i].media_info.collections) ? 1: 0;
        output_result ( result, result_string, "Media Collections", "Incorrect" );
    }

    /* Cassette */
    main_result += result = string_compare(sio.cassette.description, 
            sioOrig.cassette.description) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Description", "Incorrect" );
    main_result += result = string_compare(sio.cassette.dev.filename, 
            sioOrig.cassette.dev.filename) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Filename", "Incorrect" );
    main_result += result = (sio.cassette.record_pressed != 
            sioOrig.cassette.record_pressed ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Record Pressed", "Incorrect" );
    main_result += result = (sio.cassette.motor_gap_delay != 
            sioOrig.cassette.motor_gap_delay ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Motor Gap Delay", "Incorrect" );
    main_result += result = (sio.cassette.dev.buffer_size != 
            sioOrig.cassette.dev.buffer_size ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Buffer Size", "Incorrect" );
    main_result += result = (sio.cassette.dev.num_blocks != 
            sioOrig.cassette.dev.num_blocks ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Num Blocks", "Incorrect" );
    main_result += result = (sio.cassette.dev.current_block != 
            sioOrig.cassette.dev.current_block ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Current Block", "Incorrect" );
    main_result += result = (sio.cassette.dev.current_byte != 
            sioOrig.cassette.dev.current_byte ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Current Byte", "Incorrect" );
    main_result += result = (sio.cassette.dev.expected_bytes != 
            sioOrig.cassette.dev.expected_bytes ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Expected Bytes", "Incorrect" );
    main_result += result = (sio.cassette.dev.header_size != 
            sioOrig.cassette.dev.header_size ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Header Size", "Incorrect" );
    main_result += result = (sio.cassette.dev.baud != 
            sioOrig.cassette.dev.baud ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Baud", "Incorrect" );
    main_result += result = (sio.cassette.dev.action_time != 
            sioOrig.cassette.dev.action_time ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Action Time", "Incorrect" );
    main_result += result = (sio.cassette.dev.status != 
            sioOrig.cassette.dev.status ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Status", "Incorrect" );
    main_result += result = (sio.cassette.dev.type != 
            sioOrig.cassette.dev.type ) ? 1 : 0;
    output_result ( result, "Sio Cassette", "Type", "Incorrect" );
    main_result += result = mem_compare(sio.cassette.dev.f_buffer, 
            sioOrig.cassette.dev.f_buffer, 
            sizeof(KBYTE) * sio.cassette.dev.buffer_size) ? 1: 0;
    output_result ( result, "Sio Cassette", "F Buffer", "Incorrect" );
    main_result += result = (sio.cassette.dev.current_data != 
            &sio.cassette.dev.f_buffer[40]) ? 1 : 0; 
    output_result ( result, "Sio Cassette", "Current Data", "Incorrect" );
    main_result += result = memcmp ( sio.cassette.dev.header, 
                sioOrig.cassette.dev.header, sizeof(KBYTE) * 16 ) ? 1: 0;
    output_result ( result, "Sio Cassette", "Header", "Incorrect" );
    main_result += result = memcmp ( sio.cassette.dev.data_frame, 
            sioOrig.cassette.dev.data_frame, sizeof(KBYTE) * 260 ) ? 1: 0;
    output_result ( result, "Sio Cassette", "Data Frame", "Incorrect" );

    int cas_result = 0;
    for ( i = 0; i < MAX_CAS_BLOCKS; ++i ) {
        cas_result += result = (sio.cassette.blk[i].data_ptr != 
                &sio.cassette.dev.f_buffer[i/12]) ? 1 : 0; 
        cas_result += result = (sio.cassette.blk[i].size != 
                sioOrig.cassette.blk[i].size ) ? 1 : 0;
        cas_result += result = (sio.cassette.blk[i].mark_tone_size != 
                sioOrig.cassette.blk[i].mark_tone_size ) ? 1 : 0;
    }
    output_result ( cas_result, "Sio Cassette", "Blocks", "Incorrect" );
    main_result += cas_result;

    main_result += result = string_compare(sio.cassette.media_info.title,sioOrig.cassette.media_info.title) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Title", "Incorrect" );
    main_result += result = string_compare(sio.cassette.media_info.file,sioOrig.cassette.media_info.file) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media File", "Incorrect" );
    main_result += result = string_compare(sio.cassette.media_info.md5,sioOrig.cassette.media_info.md5) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media MD5", "Incorrect" );
    main_result += result = string_compare(sio.cassette.media_info.sha1,sioOrig.cassette.media_info.sha1) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media SHA1", "Incorrect" );
    main_result += result = (sio.cassette.media_info.media_type != sioOrig.cassette.media_info.media_type) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Type", "Incorrect" );
    main_result += result = (sio.cassette.media_info.mapping != sioOrig.cassette.media_info.mapping) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Mapping", "Incorrect" );
    main_result += result = (sio.cassette.media_info.image_size != sioOrig.cassette.media_info.image_size) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Image Size", "Incorrect" );
    main_result += result = (sio.cassette.media_info.write_protect != sioOrig.cassette.media_info.write_protect) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Write Protect", "Incorrect" );
    main_result += result = (sio.cassette.media_info.crc != sioOrig.cassette.media_info.crc) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media CRC", "Incorrect" );
    main_result += result = (sio.cassette.media_info.collections != sioOrig.cassette.media_info.collections) ? 1: 0;
    output_result ( result, "Sio Cassette", "Media Collections", "Incorrect" );

    main_result += run_large_cart_bank_state_round_trip ( &myAtari, 0x40000,
            "large_cart_256k", "256K" );
    main_result += run_large_cart_bank_state_round_trip ( &myAtari, 0x80000,
            "large_cart_512k", "512K" );

    main_result += run_hybrid_state_round_trip ( &myAtari, TEST_HYB_MODE_8K, 5,
            "hybrid_8k", "8K mode" );
    main_result += run_hybrid_state_round_trip ( &myAtari, TEST_HYB_MODE_16K, 3,
            "hybrid_16k", "16K mode" );

    /* Hybrid latch clamp: out-of-range bank indexes must reset to 0. */
    {
        int clamp_mode, clamp_b8, clamp_b16, clamp_result;
        memory_init_bank ( 0x80000 );
        mem_set_hyb_state ( TEST_HYB_MODE_8K, 9999, 0 );
        mem_get_hyb_state ( &clamp_mode, &clamp_b8, &clamp_b16 );
        clamp_result = ( clamp_b8 != 0 ) ? 1 : 0;
        mem_set_hyb_state ( TEST_HYB_MODE_16K, 0, 9999 );
        mem_get_hyb_state ( &clamp_mode, &clamp_b8, &clamp_b16 );
        clamp_result += ( clamp_b16 != 0 ) ? 1 : 0;
        main_result += clamp_result;
        output_result ( clamp_result, "Hybrid Cart State", "Bank clamp",
                "Out-of-range hybrid bank index was not clamped to 0" );
    }

    /* Recalling an SD-less state must clear any previously loaded SD backing. */
    {
        unsigned char sd_seed[0x10] = { 0 };
        int stale_result;
        memory_init_sd ( sizeof(sd_seed) );
        memory_write_sd_bytes ( 0, sd_seed, sizeof(sd_seed) );
        main_result += states_recall ( "large_cart_256k", &myAtari );
        stale_result = ( mem_get_sd_image() != NULL ||
                         mem_get_sd_image_size() != 0 ) ? 1 : 0;
        main_result += stale_result;
        output_result ( stale_result, "Hybrid Cart State", "SD cleared",
                "Stale SD backing survived recall of an SD-less state" );
        memory_clear_sd ();
    }

    /* Free allocated memory */
    for ( i = 0; i < MAX_SIO_DEVICES; ++i ) {
        free(sioOrig.drives[i].dev.f_buffer);
        free(sio.drives[i].dev.f_buffer);
        media_exit ( &sioOrig.drives[i].media_info );
        media_exit ( &sio.drives[i].media_info );
    }

    free(sioOrig.cassette.dev.f_buffer);
    free(sio.cassette.dev.f_buffer);
    media_exit ( &sioOrig.cassette.media_info );
    media_exit ( &sio.cassette.media_info );
    media_exit ( &cartOrig.media_info );
    media_exit ( &cart_rightOrig.media_info );
    media_exit ( &cart.media_info );
    media_exit ( &cart_right.media_info );

    return main_result;

} /* end main */
