/*

    6505 Emulator.
    2007 Jason Hunt
    nulluser@gmail.com
*/

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

#include "emu.h"

bool debug = 0;

emu proc;





void start_console( void )
{
}



void write_console(char *fmt, ... )
{
    static HANDLE h_console = INVALID_HANDLE_VALUE;

    if (h_console == INVALID_HANDLE_VALUE)
    {
        AllocConsole();
        h_console =  GetStdHandle(STD_OUTPUT_HANDLE);
    }
   
    const unsigned int max = 1024;

    va_list args;
    char text[max];

    va_start(args, fmt);
    vsnprintf(text, max, fmt, args);
    va_end(args);    
    
   DWORD to_screen = 0;
   
   WriteConsole(h_console, text, strlen(text), &to_screen, NULL);
}







void emu_start ( void )
{

    proc.load_program("asm\\test.obj");

    proc.init();
//    proc.run();`

    write_console("Memory Dump:\n");
    
    unsigned int addr = 0;

    unsigned int size = 0x100;

  //  while (addr < MEM_SIZE)
    {
        proc.dump(addr, addr+size);
        addr += size;
    //    getchar();
    }
}


void emu_process( void )
{
    static int count = 0;
    count++;
    //    if (count > 1) return;
        
        if (proc.get_fail()) return;
    
    for (int i = 0; i < 2000;i++)
    {
        proc.show_state();
        proc.decode();
        proc.check_flags();    
    }
    
    
}




emu::emu( void )
{
    load_op_map();
}
    


void emu::set_mem( unsigned int a, unsigned char v )
{
    // Grab console output    
    if (a == 0x1000)
    {
        if (debug)
            write_console("OUT: (%c)\n", v);
        else
            write_console("%c", v);        
    }
    else 
        mem[a& 0xffff] = v;
}



void emu::inv( void ) 
{
    write_console("INV\n"); 
    fail = 1;
}


void emu::adc(void) 
{
  if (debug) write_console("ADC  src: %x\n", src_8);
  
  acc += src_8;  
    
} 




void emu::and1(void) 
{
    acc &= src_8    ;
    
    
}




void emu::asl(void){}
void emu::bcc(void){}
void emu::bcs(void){}

void emu::beq(void)
{
    if (f_z) pc += (char)p1;    
    if (debug) write_console("BEQ %d\n", (char)p1);
}


void emu::bit(void){}    


void emu::bmi(void)
{
       if (debug)  write_console("BMI  %d\n", f_n);

    if (f_n) pc += (char)p1; 
}



void emu::bne(void)
{
    if (!f_z) pc += (char)p1;    

    if (debug) write_console("BNE %d  PC: %x\n", (char)p1 + 1, pc);
}
            
            
void emu::bpl(void){}
void emu::brk(void){ fail=1;}    
void emu::bvc(void){}
void emu::bvs(void){}
void emu::clc(void){}
void emu::cld(void){}    
void emu::cli(void){} 
void emu::clv(void){}



void emu::cmp(void)
{
    word r = acc - src_8;   

    f_z = (acc ==src_8) ? 1 : 0;
//    f_c = (r < 0x100) ? 1 : 0;
    f_n = (acc<src_8) ? 1 : 0;

    if (debug) write_console("CMP %d z: %d   n: %d  %d\n",r, f_z, f_n, r&0xff);

}



void emu::cpx(void){}    
void emu::cpy(void){} 
void emu::dec(void){} 
void emu::dex(void){}
void emu::dey(void){}    
void emu::eor(void){}

void emu::inc( void ) 
{
    if (debug) write_console("INC "); 

    if (mode == m_ab)
    {
        if (debug) write_console("%x AB\n", ab_addr);

        mem[ab_addr]++;
    }





}
   
//     void emu::inc(void){}
void emu::inx(void){}


void emu::iny(void)
{
    if (debug) write_console("INY\n");
    iy++;
    
    if (iy==0) f_z = iy == 0;
}   



void emu::jmp( void ) 
{
    if (debug) write_console("JMP ");

    if (mode == m_ab)
    {
        if (debug) write_console(" %x AB\n", ab_addr);
          pc = ab_addr-3;
    }
}


void emu::jsr( void ) 
{
    if (debug)   write_console("JSR "); 

    if (mode == m_ab)
    {
        if (debug) write_console("%x AB\n", ab_addr);

        // Pust the PC +2 to the stack
        pc += 3;
        mem[sp--] = pc >> 8;
        mem[sp--] = pc & 0xff;
        
       pc = ab_addr-3;
        
    }
}



void emu::lda( void ) 
{
    if (debug) write_console("LDA \n "); 

    acc = src_8;

f_z = acc == 0;
f_n = 0;


}

void emu::ldx(void){}    


void emu::ldy(void)
{
    if (debug) write_console("LDY");

    iy = src_8;
}


void emu::lsr(void)
{
    
    src_8 >>= 1;
    
    if (mode = m_acc)
    {
        //write_console("LSR acc    %x   %x\n", acc, src_8);
        
     acc = src_8;   
        //write_console("LSR acc    %x\n", acc);
        
    }        
        
    
    
}


void emu::nop( void ) 
{
 //   write_console("NOP\n"); 
}

void emu::ora(void){}    


void emu::pha(void)
{
    mem[sp--] = acc;
    if (debug) write_console("PHA  %x\n", acc);
}


void emu::php(void){}

void emu::pla(void)
{
    acc = mem[++sp]; f_z = (acc == 0) ? 1 : 0; f_n = 0;

    if (debug) write_console("PLA   %x\n", acc);
}



void emu::plp(void){}   
void emu::rol(void){}
void emu::ror(void){}
void emu::rti(void){}


void emu::rts(void)
{
    if (debug) write_console("RTS:  SP:  %x  get16 :  %x  PC: %x   \n", sp,get_16(sp+1), get_16(sp+1) -3);
    
    pc = get_16(sp+1) - 1;
    sp += 2;
}   



void emu::sbc(void){}
void emu::sec(void){}
void emu::sed(void){}
void emu::sei(void){}

void emu::sta( void ) 
{
    if (debug)  write_console("STA "); 

    if (mode == m_ab)
    {
        if (debug) write_console(" %x AB \n", ab_addr);
        
        set_mem(ab_addr, acc);
    }

    if (mode == m_zp)
    {
        if (debug)  write_console(" %x ZP \n", p1);
        
        set_mem(p1, acc);
    }
}
 
void emu::stx(void){}
void emu::sty(void){}
void emu::tax(void){}
    
void emu::tay(void)
{ 
    if (debug) write_console("TAY\n");
    iy = acc; 
}


void  emu::tsx(void){}
void  emu::txa(void){}
void  emu::txs(void){}    

void  emu::tya(void)
{
    if (debug) write_console("TYA\n");
    acc = iy;    
}



word emu::get_16(word a)
{
    return(mem[a] + mem[a+1]*256);
    
}

void emu::get_src(void)
{
//    write_console("mode: %d\n", mode);
    
      // Decode the size
    switch(mode)
    {
//        case m_inv: op_map[num].size = 1; break;
//        case m_imp: op_map[num].size = 1; break;

        case m_acc: src_8 = acc; break;

        case m_imm: src_8 = p1; break;
        case m_zp:  src_8 = mem[p1]; break;
        case m_zpx: src_8 = mem[p1+ix]; break;
        case m_zpy: src_8 = mem[p1+iy]; break;
        case m_rel: src_8 = p1; break;
        case m_ab:  src_16 = ab_addr; break;
        case m_abx: src_16 = ab_addr; break;
        case m_aby: src_16 = ab_addr; break;
        case m_ind: src_16 = get_16(ab_addr); break;
        case m_idx: src_8 = mem[get_16(p1+ix)]; break;
        case m_idy: src_8 = mem[get_16(p1)+iy]; break;
    }  
    
    
    
    
    
}















void emu::decode(void)
{
    op = mem[pc];

    inst = op_map[op].inst;
    mode = op_map[op].mode;
    sz = op_map[op].size;
    
    p1 = mem[(pc+1) & 0xffff];
    p2 = mem[(pc+2) & 0xffff];

    ab_addr = (int)p1 + ((int)(p2)<<8);

    get_src();

       
    (this->*op_map[op].func)();  // Call the operation function
    
    pc += sz;    // Advance PC
}


void emu::init( void )
{
    // Jump to boot vector
    pc = mem[0xfffe] + mem[0xffff] << 8;

    sp = 0x01ff;
 
    acc = 0;
    ix = 0;
    iy = 0;

    f_c = 0;
    f_z = 1;    
    f_i = 0;    
    f_d = 0;    
    f_b = 0;    
    f_v = 0;    
    f_n = 0;    


    
    fail = 0;
}


void emu::dump(unsigned int start, unsigned int end)
{
    unsigned int id = start;

    while (id < end)
    {
        write_console("%04x ", id);
    
        for (int j = 0; j < 16; j++)    
            write_console("%02x ", mem[id++]);    

        write_console(" ");

        id -= 16;
    
        for (int j = 0; j < 16; j++)    
        {
            byte d = mem[id++];
            if (d >= 0x32 && d <= 0x7f)
                write_console("%c", d);   
                else
                write_console("."); 
        }
        
        write_console("\n");
        
    }   
}


void emu::show_state( void )
{
   if (!debug) return;
    
    write_console("PC: %4x SP: %3x OP: %2x A: %2x X: %2x Y: %2x   C: %1d Z: %1d O: %1d N: %1d\n", 
            pc,  sp, op,acc, ix,  iy  , f_c, f_z, f_v, f_n );
}


void emu::check_flags(void)
{
    f_z = (acc == 0) ? 1 : 0;
 
 if (sp > 0x1ff)
 {
    printf("SP > 0x1ff\n");        
     fail = 1;   
    } 
 
}


void emu::run ( void  )
{
    write_console("Starting emulation\n");

    init();
    
    bool done = false;

    while (!done && !fail)
    {
        show_state();
        decode();
        check_flags();
        
       // Sleep(500);
    }      
}


void emu::load_memory(byte * data, unsigned int size)
{
    if (size > MEM_SIZE) return;
   
    memcpy(mem, data, size);
}


/* Load MOS hex file */
void emu::load_program( char * filename )
{
    write_console("Loading %s\n", filename);
    
    FILE *f = fopen(filename, "rt");

    if(!f) { write_console("Unable to open (%s)\n", filename); return; }
   
    while(1)
    {
        unsigned int num,addr,data;
    
        fscanf(f, ";");
        
        if (fscanf(f, "%02x", &num) == EOF) break;
       
        if (num == 0) break;
       
        if (fscanf(f, "%04x", &addr) == EOF) break;

        for (unsigned int i = 0; i < num; i++)
        {
            if (fscanf(f, "%02x", &data) == EOF) break;
            
            mem[addr++] = data;
        }
        
        // Ignore checksum
        if (fscanf(f, "%04x\n", &addr) == EOF) break;
    }
 
    fclose(f);
}
/* End of load program */



void emu::set_op(byte num, byte inst, byte mode)
{
    op_map[num].inst = inst;
    op_map[num].mode = mode;
}


void emu::set_func(byte num, void (emu::*func) (void), byte flag_mask)
{
    byte m = op_map[num].mode;
    
enum mode_list  { m_inv, m_imp, m_acc, m_imm, 
                m_zp,  m_zpx, m_zpy, m_rel,
                  m_ab,  m_abx, m_aby, m_ind, m_idx, m_idy};
    
    // Decode the size
    switch(op_map[num].mode)
    {
        case m_inv: op_map[num].size = 1; break;
        case m_imp: op_map[num].size = 1; break;
        case m_acc: op_map[num].size = 1; break;
        case m_imm: op_map[num].size = 2; break;
        case m_zp:  op_map[num].size = 2; break;
        case m_zpx: op_map[num].size = 2; break;
        case m_zpy: op_map[num].size = 2; break;
        case m_rel: op_map[num].size = 2; break;
        case m_ab:  op_map[num].size = 3; break;
        case m_abx: op_map[num].size = 3; break;
        case m_aby: op_map[num].size = 3; break;
        case m_ind: op_map[num].size = 3; break;
        case m_idx: op_map[num].size = 2; break;
        case m_idy: op_map[num].size = 2; break;
    }


    op_map[num].func = func;
    op_map[num].flag_mask = flag_mask;
}


/* Create internal opcode map */
void emu::load_op_map ( void )
{
    // Clear opcode table
    for (int i = 0; i < 256; i++)
    {
         set_op(i, i_inv, m_inv);//, &emu::nop);
         set_func(i, &emu::inv, 0);
    }

     // Link each valid opcode to the associated instruction and addressing mode
    set_op(0x00, i_brk, m_imm); set_op(0x01, i_ora, m_idx); set_op(0x05, i_ora, m_zp);  set_op(0x06, i_asl, m_zp);
    set_op(0x08, i_php, m_imp); set_op(0x09, i_ora, m_imm); set_op(0x0A, i_asl, m_acc); set_op(0x0D, i_ora, m_ab);
    set_op(0x0E, i_asl, m_ab);  set_op(0x10, i_bpl, m_rel); set_op(0x11, i_ora, m_idy); set_op(0x15, i_ora, m_zpx);
    set_op(0x16, i_asl, m_zpx); set_op(0x18, i_clc, m_imp); set_op(0x19, i_ora, m_aby); set_op(0x1D, i_ora, m_abx);
    set_op(0x1E, i_asl, m_abx); set_op(0x20, i_jsr, m_ab);  set_op(0x21, i_and, m_idx); set_op(0x24, i_bit, m_zp);
    set_op(0x25, i_and, m_zp);  set_op(0x26, i_rol, m_zp);  set_op(0x28, i_plp, m_imp); set_op(0x29, i_and, m_imm);
    set_op(0x2A, i_rol, m_acc); set_op(0x2C, i_bit, m_ab);  set_op(0x2D, i_and, m_ab);  set_op(0x2E, i_rol, m_ab);
    set_op(0x30, i_bmi, m_rel); set_op(0x31, i_and, m_idy); set_op(0x35, i_and, m_zpx); set_op(0x36, i_rol, m_zpx);
    set_op(0x38, i_sec, m_imp); set_op(0x39, i_and, m_aby); set_op(0x3D, i_and, m_abx); set_op(0x3E, i_rol, m_abx);
    set_op(0x40, i_rti, m_imp); set_op(0x41, i_eor, m_idx); set_op(0x45, i_eor, m_zp);  set_op(0x46, i_lsr, m_zp);
    set_op(0x48, i_pha, m_imp); set_op(0x49, i_eor, m_imm); set_op(0x4A, i_lsr, m_acc); set_op(0x4C, i_jmp, m_ab);
    set_op(0x4D, i_eor, m_ab);  set_op(0x4E, i_lsr, m_ab);  set_op(0x50, i_brk, m_abx); set_op(0x50, i_bvc, m_imp);
    set_op(0x51, i_eor, m_idy); set_op(0x55, i_eor, m_zpx); set_op(0x56, i_lsr, m_zpx); set_op(0x58, i_cli, m_imp);
    set_op(0x59, i_eor, m_aby); set_op(0x5E, i_lsr, m_abx); set_op(0x60, i_rts, m_imp); set_op(0x61, i_adc, m_idx);
    set_op(0x65, i_adc, m_zp);  set_op(0x66, i_ror, m_zp);  set_op(0x68, i_pla, m_imp); set_op(0x69, i_adc, m_imm);
    set_op(0x6A, i_ror, m_acc); set_op(0x6C, i_jmp, m_ind); set_op(0x6D, i_adc, m_ab);  set_op(0x6E, i_ror, m_ab);
    set_op(0x70, i_brk, m_abx); set_op(0x70, i_bvs, m_imp); set_op(0x71, i_adc, m_idy); set_op(0x75, i_adc, m_zpx);
    set_op(0x76, i_ror, m_zpx); set_op(0x78, i_sei, m_imp); set_op(0x79, i_adc, m_aby); set_op(0x7E, i_ror, m_abx);
    set_op(0x80, i_sta, m_ab);  set_op(0x81, i_sta, m_idx); set_op(0x84, i_sty, m_zp);  set_op(0x85, i_sta, m_zp);
    set_op(0x86, i_stx, m_zp);  set_op(0x88, i_dey, m_imp); set_op(0x8A, i_txa, m_imp); set_op(0x8C, i_sty, m_ab); 
    set_op(0x8D, i_sta, m_ab);  set_op(0x8E, i_stx, m_ab);  set_op(0x90, i_bcc, m_rel); set_op(0x90, i_sta, m_abx);
    set_op(0x91, i_sta, m_idy); set_op(0x94, i_sty, m_zpx); set_op(0x95, i_sta, m_zpx); set_op(0x96, i_stx, m_zpy);
    set_op(0x98, i_tya, m_imp); set_op(0x99, i_sta, m_aby); set_op(0x9A, i_txs, m_imp); set_op(0xA0, i_ldy, m_imm);
    set_op(0xA1, i_lda, m_idx); set_op(0xA2, i_ldx, m_imm); set_op(0xA4, i_ldy, m_zp);  set_op(0xA5, i_lda, m_zp);
    set_op(0xA6, i_ldx, m_zp);  set_op(0xA8, i_tay, m_imp); set_op(0xA9, i_lda, m_imm); set_op(0xAA, i_tax, m_imp);
    set_op(0xAC, i_ldy, m_ab);  set_op(0xAD, i_lda, m_ab);  set_op(0xAE, i_ldx, m_ab);  set_op(0xB0, i_bcs, m_rel);
    set_op(0xB1, i_lda, m_idy); set_op(0xB4, i_ldy, m_zpx); set_op(0xB5, i_lda, m_zpx); set_op(0xB6, i_ldx, m_zpy);
    set_op(0xB8, i_clv, m_imp); set_op(0xB9, i_lda, m_aby); set_op(0xBA, i_tsx, m_imp); set_op(0xBC, i_ldy, m_abx);
    set_op(0xBD, i_lda, m_abx); set_op(0xBE, i_ldx, m_aby); set_op(0xC0, i_cpy, m_imm); set_op(0xC1, i_cmp, m_idx);
    set_op(0xC4, i_cpy, m_zp);  set_op(0xC5, i_cmp, m_zp);  set_op(0xC6, i_dec, m_zp);  set_op(0xC8, i_iny, m_imp);
    set_op(0xC9, i_cmp, m_imm); set_op(0xCA, i_dex, m_imp); set_op(0xCC, i_cpy, m_ab);  set_op(0xCD, i_cmp, m_ab);
    set_op(0xCE, i_dec, m_ab);  set_op(0xD0, i_bne, m_rel); set_op(0xD1, i_cmp, m_idy); set_op(0xD5, i_cmp, m_zpx); 
    set_op(0xD6, i_dec, m_zpx); set_op(0xD8, i_cld, m_imp); set_op(0xD9, i_cmp, m_aby); set_op(0xDD, i_cmp, m_abx);
    set_op(0xDE, i_dec, m_abx); set_op(0xE0, i_cpx, m_imm); set_op(0xE1, i_sbc, m_idx); set_op(0xE4, i_cpx, m_zp); 
    set_op(0xE5, i_sbc, m_zp);  set_op(0xE6, i_inc, m_zp);  set_op(0xE8, i_inx, m_imp); set_op(0xE9, i_sbc, m_imm);
    set_op(0xEA, i_nop, m_imp); set_op(0xEC, i_cpx, m_ab);  set_op(0xED, i_sbc, m_ab);  set_op(0xEE, i_inc, m_ab);
    set_op(0xF0, i_beq, m_rel); set_op(0xF1, i_sbc, m_idy); set_op(0xF5, i_sbc, m_zpx); set_op(0xF6, i_inc, m_zpx);
    set_op(0xF8, i_sed, m_imp); set_op(0xF9, i_sbc, m_aby); set_op(0xFD, i_sbc, m_abx); set_op(0xFE, i_inc, m_abx);
       
    // Link functions points and set flag masks
    for (int i = 0; i < 256; i++)
    {
        switch(op_map[i].inst)
        {
            case i_inv: set_func(i, &emu::inv, 0); break;
            case i_adc: set_func(i, &emu::adc, 0); break;
            case i_and: set_func(i, &emu::and1, 0); break;
            case i_asl: set_func(i, &emu::asl, 0); break;
            case i_bcc: set_func(i, &emu::bcc, 0); break;
            case i_bcs: set_func(i, &emu::bcs, 0); break;
            case i_beq: set_func(i, &emu::beq, 0); break;
            case i_bit: set_func(i, &emu::bit, 0); break;
            case i_bmi: set_func(i, &emu::bmi, 0); break;
            case i_bne: set_func(i, &emu::bne, 0); break;
            case i_bpl: set_func(i, &emu::bpl, 0); break;
            case i_brk: set_func(i, &emu::brk, 0); break;
            case i_bvc: set_func(i, &emu::bvc, 0); break;
            case i_bvs: set_func(i, &emu::bvs, 0); break;
            case i_clc: set_func(i, &emu::clc, 0); break;
            case i_cld: set_func(i, &emu::cld, 0); break;
            case i_cli: set_func(i, &emu::cli, 0); break;
            case i_clv: set_func(i, &emu::clv, 0); break;
            case i_cmp: set_func(i, &emu::cmp, 0); break;
            case i_cpx: set_func(i, &emu::cpx, 0); break;
            case i_cpy: set_func(i, &emu::cpy, 0); break;
            case i_dec: set_func(i, &emu::dec, 0); break;
            case i_dex: set_func(i, &emu::dex, 0); break;
            case i_dey: set_func(i, &emu::dey, 0); break;
            case i_eor: set_func(i, &emu::eor, 0); break;
            case i_inc: set_func(i, &emu::inc, 0); break;
            case i_inx: set_func(i, &emu::inx, 0); break;
            case i_iny: set_func(i, &emu::iny, 0); break;
            case i_jmp: set_func(i, &emu::jmp, 0); break;
            case i_jsr: set_func(i, &emu::jsr, 0); break;
            case i_lda: set_func(i, &emu::lda, 0); break;
            case i_ldx: set_func(i, &emu::ldx, 0); break;
            case i_ldy: set_func(i, &emu::ldy, 0); break;
            case i_lsr: set_func(i, &emu::lsr, 0); break;
            case i_nop: set_func(i, &emu::nop, 0); break;
            case i_ora: set_func(i, &emu::ora, 0); break;
            case i_pha: set_func(i, &emu::pha, 0); break;
            case i_php: set_func(i, &emu::php, 0); break;
            case i_pla: set_func(i, &emu::pla, 0); break;
            case i_plp: set_func(i, &emu::plp, 0); break;
            case i_rol: set_func(i, &emu::rol, 0); break;
            case i_ror: set_func(i, &emu::ror, 0); break;
            case i_rti: set_func(i, &emu::rti, 0); break;
            case i_rts: set_func(i, &emu::rts, 0); break;
            case i_sbc: set_func(i, &emu::sbc, 0); break;
            case i_sec: set_func(i, &emu::sec, 0); break;
            case i_sed: set_func(i, &emu::sed, 0); break;
            case i_sei: set_func(i, &emu::sei, 0); break;
            case i_sta: set_func(i, &emu::sta, 0); break;
            case i_stx: set_func(i, &emu::stx, 0); break;
            case i_sty: set_func(i, &emu::sty, 0); break;
            case i_tax: set_func(i, &emu::tax, 0); break;
            case i_tay: set_func(i, &emu::tay, 0); break;
            case i_tsx: set_func(i, &emu::tsx, 0); break;
            case i_txa: set_func(i, &emu::txa, 0); break;
            case i_txs: set_func(i, &emu::txs, 0); break;
            case i_tya: set_func(i, &emu::tya, 0); break;
        }        
    }
        
    
    
}












