/*
Copyright (C) 1997-2008 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )

http://www.zsnes.com
http://sourceforge.net/projects/zsnes
https://zsnes.bountysource.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <ctype.h>
#include <string.h>
#ifndef NCURSES
#include <curses.h>
#else
#include <ncurses.h>
#endif
#include "zpath.h"

#ifdef __MSDOS__
#include <dpmi.h>
#endif // __MSDOS__

#include "c_vcache.h"
#include "cpu/c_execute.h"
#include "cpu/memory.h"
#include "cpu/memtable.h"
#include "cpu/regs.h"
#include "cpu/spc700.h"
#include "debugasm.h"
#include "endmem.h"
#include "init.h"
#include "initc.h"
#include "zstate.h"

// All of these should be in headers, people!

extern unsigned char oamram[1024], DSPMem[256];

extern unsigned char CurrentCPU;

extern unsigned char soundon;
extern unsigned int cycpbl;

extern unsigned short xa, xx, xy, xs;
extern unsigned char xdb;

extern unsigned char debuggeron;

// should be in "zstate.h"
void debugloadstate();

char* ocname;
unsigned char addrmode[];
char* spcnametab[];
char* AddressTable[];
unsigned char ArgumentTable[];

unsigned char debugds = 0; // debug disable (bit 0 = 65816, bit 1 = SPC)
unsigned int numinst = 0;

unsigned char wx = 0, wy = 0, wx2 = 0, wy2 = 0;
unsigned char execut = 0;
unsigned char debstop = 0, debstop2 = 0, debstop3 = 0, debstop4 = 0;

char debugsa1 = 0;
char skipdebugsa1 = 1;

#define CP(n) (A_BOLD | COLOR_PAIR(n))

enum color_pair {
    cp_white = 1,
    cp_magenta,
    cp_red,
    cp_cyan,
    cp_green,
    cp_yellow,
    cp_white_on_blue,
};

WINDOW* debugwin;

// can't get this to work right...
//#define CHECK (COLOR_PAIR(cp_white) | A_DIM | ACS_CKBOARD)
#define CHECK (CP(cp_white) | ' ')

void debugloop();

void startdisplay();
void nextopcode();
void cleardisplay();
void nextspcopcode();
void SaveOAMRamLog();
void debugdump();
void out65816();

void traceops(unsigned count);
void SPCbreakops(unsigned short addr);

unsigned char* findop();
unsigned char* findoppage();

void startdebugger(void)
{
    static int firsttime = 1;

#ifdef __MSDOS__
    __dpmi_regs regs;
    regs.x.ax = 0x0003;
    __dpmi_int(0x10, &regs);
#endif // __MSDOS__

    curblank = 0x40;
    debuggeron = 1;

    if (firsttime) {
        initscr();
        cbreak();
        noecho();
        intrflush(stdscr, FALSE);
        keypad(stdscr, TRUE);

        /* set up colors */
        start_color();
        init_pair(cp_white, COLOR_WHITE, COLOR_BLACK);
        init_pair(cp_magenta, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(cp_red, COLOR_RED, COLOR_BLACK);
        init_pair(cp_cyan, COLOR_CYAN, COLOR_BLACK);
        init_pair(cp_green, COLOR_GREEN, COLOR_BLACK);
        init_pair(cp_yellow, COLOR_YELLOW, COLOR_BLACK);
        init_pair(cp_white_on_blue, COLOR_WHITE, COLOR_BLUE);
    }

    execut = 0;

    if (firsttime) {
        startdisplay();

        debugwin = newwin(20, 77, 2, 1);

        wbkgd(debugwin, CP(cp_white_on_blue) | ' ');
        // wattrset(debugwin, CP(cp_white_on_blue));

        scrollok(debugwin, TRUE);
        idlok(debugwin, TRUE);

        firsttime = 0;
    } else {
        touchwin(stdscr);
        touchwin(debugwin);
        refresh();
        wrefresh(debugwin);
    }

    debugloop();
    cleardisplay();

    // "pushad / call LastLog / ... / popad" elided
    SaveOAMRamLog();

    if (execut == 1) {
        start65816();
        return;
    }
    endprog();
    return;
}

WINDOW* openwindow(int nlines, int ncols, int begin_y, int begin_x,
    char* message)
{
    WINDOW* w = newwin(nlines, ncols, begin_y, begin_x);
    wbkgd(w, CP(cp_white_on_blue | ' '));
    // wattrset(w, CP(cp_white_on_blue));

    mvwprintw(w, 1, 1, "%s", message);
    wclrtoeol(w);
    box(w, 0, 0);

    return w;
}

void closewindow(WINDOW* w)
{
    delwin(w);
    touchwin(debugwin);
    wrefresh(debugwin);
}

//*******************************************************
// Debug Loop
//*******************************************************

unsigned short PrevBreakPt_offset;
unsigned char PrevBreakPt_page;

void debugloop()
{
    int key;
a:
    if (!(debugds & 2))
        nextopcode();
    if (!(debugds & 1))
        nextspcopcode();

b:
    // redrawing the display is always a good idea
    refresh();
    wrefresh(debugwin);

    key = getch();
    if (key >= 0 && key < 256)
        key = toupper(key);

    switch (key) {
    case KEY_F(1): // run
        execut = 1;
        return;

    case KEY_F(2): // debugsavestate
        statesaver();
        goto b;

    case KEY_F(4): // debugloadstate
        debugloadstate();
        goto a;

    case 27: // exit
        return;

    case '\n': // step
        goto e;

    case 'C': // clear
        numinst = 0;
        goto a;

    case 'M': // modify
    {
        WINDOW* w;
        unsigned addr, value, n;

        w = openwindow(7, 33, 11, 24, "    Enter Address : ");
        mvwaddstr(w, 3, 1, "    Previous Value: ");
        mvwaddstr(w, 5, 1, "    Enter Value   : ");

        wrefresh(w);

        echo();
        n = mvwscanw(w, 1, 21, "%x", &addr);
        noecho();

        if (n == 1) {
            mvwprintw(w, 3, 21, "%02x", memr8(addr >> 16, addr));
            wrefresh(w);

            echo();
            n = mvwscanw(w, 5, 21, "%x", &value);
            noecho();

            if (n == 1) {
                memw8(addr >> 16, addr, value);
            }
        }

        closewindow(w);
        goto b;
    }

    case 'B': // breakpoint
    {
        WINDOW* w = openwindow(3, 33, 11, 24, "    Enter Address : ");
        unsigned addr, n;

        wrefresh(w);

        echo();
        n = wscanw(w, "%x", &addr);
        noecho();

        closewindow(w);

        if (n == 1) {
            w = openwindow(3, 52, 11, 14,
                "   Locating Breakpoint ... Press ESC to stop.    ");
            wrefresh(w);
            nodelay(stdscr, 1);

            PrevBreakPt_page = addr >> 16;
            PrevBreakPt_offset = addr;
            breakops();

            nodelay(stdscr, 0);
            closewindow(w);

            goto a;
        }

        goto b;
    }

    case 'R': // repeat breakpoint
        breakops();
        goto a;

    case 'S': // SPC breakpoint
    {
        WINDOW* w;
        unsigned addr, n;

        w = openwindow(3, 33, 11, 24, "     Enter Address : ");
        wrefresh(w);

        echo();
        n = mvwscanw(w, 1, 22, "%x", &addr);
        noecho();

        closewindow(w);

        if (n == 1) {
            SPCbreakops(addr);
            goto a;
        }
        goto b;
    }

    case 'A': // SPC modify
    {
        WINDOW* w;
        unsigned addr, value, n;

        w = openwindow(7, 33, 11, 24, "     Enter Address : ");
        mvwaddstr(w, 3, 1, "     Previous Value: ");
        mvwaddstr(w, 5, 1, "     Enter Value   : ");

        wrefresh(w);

        echo();
        n = mvwscanw(w, 1, 22, "%x", &addr);
        noecho();

        addr &= 0xFFFF;

        if (n == 1) {
            mvwprintw(w, 3, 22, "%02x", SPCRAM[addr]);
            wrefresh(w);

            echo();
            n = mvwscanw(w, 5, 22, "%x", &value);
            noecho();

            if (n == 1) {
                SPCRAM[addr] = value;
            }
        }

        closewindow(w);
        goto b;
    }

    case 'T': // trace
    {
        WINDOW* w;
        unsigned n, instrs;

        w = openwindow(3, 52, 11, 14, "   Enter # of Instructions to Trace : ");
        wrefresh(w);

        echo();
        n = wscanw(w, "%d", &instrs);
        noecho();

        closewindow(w);
        if (n == 1) {
            traceops(instrs);
            goto a;
        }

        goto b;
    }

    case 'D': // debug dump
        debugdump();
        goto b;

    case 'W': // break at signal (breakatsign)
    {
        WINDOW* w;

        w = openwindow(3, 52, 11, 14,
            "   Waiting for Signal .... Press ESC to stop.");
        wrefresh(w);

        debstop3 = 0;
        nodelay(w, TRUE);
        do {
            execnextop();
        } while ((!((++numinst % 256) && (wgetch(w) == 27)))
            && (debstop3 != 1));
        debstop3 = 0;
        nodelay(w, FALSE);

        closewindow(w);
        goto a;
    }

    case 'L': // break at signal & log (breakatsignlog)
    {
        FILE* fp;
        WINDOW *w, *real_debugwin;

        w = openwindow(3, 52, 11, 14,
            "   Waiting for Signal .... Press ESC to stop.");
        wrefresh(w);

        // Open output file
        fp = fopen_dir(ZStartPath, "debug.log", "w");

        real_debugwin = debugwin;
        debugwin = newpad(2, 77);
        scrollok(debugwin, TRUE);

        debstop3 = 0;
        nodelay(w, TRUE);
        do {
            char buf[78];
            // log instruction
            move(0, 0);
            out65816();

            mvwinnstr(debugwin, 0, 0, buf, 77);
            buf[77] = 0;
            fprintf(fp, "%s\n", buf);
            fflush(fp);

            execnextop();
        } while ((!((++numinst % 256) && (wgetch(w) == 27)))
            && (debstop3 != 1));
        debstop3 = 0;
        nodelay(w, FALSE);

        fclose(fp);
        delwin(debugwin);
        debugwin = real_debugwin;

        closewindow(w);
        goto a;
    }

    case '1': // toggle SPC
        debugds ^= 1;
        break;

    case '2': // toggle 65816
        debugds ^= 2;
        break;

    default:
        wprintw(debugwin, "Unknown key code: %d\n", key);
        goto b;
    }

e:
    skipdebugsa1 = 0;
    execnextop();
    skipdebugsa1 = 1;
    if (soundon && (debugds & 2) && (cycpbl >= 55))
        goto e;
    goto a;
}

//*******************************************************
// BreakatSignC               Breaks whenever sndwrit = 1
//*******************************************************

unsigned char sndwrit;

/* void breakatsignc() {} */

void traceops(unsigned count)
{
    WINDOW* w;

    w = openwindow(3, 52, 11, 14, "     Tracing.  Press ESC to stop.");
    wrefresh(w);

    nodelay(w, TRUE);
    while (count-- && (wgetch(w) != 27)) {
        execnextop();
    }

    closewindow(w);
}

void SPCbreakops(unsigned short addr)
{
    WINDOW* w;
    unsigned char* breakarea;

    breakarea = SPCRAM + addr;

    w = openwindow(3, 52, 11, 14, "Locating Breakpoint ... Press ESC to stop.");
    wrefresh(w);

    nodelay(w, TRUE);
    do {
        execnextop();
    } while ((!((++numinst % 256)
                 && (wgetch(w) == 27)))
        && (spcPCRam != breakarea));
    nodelay(w, FALSE);

    closewindow(w);
}

void printinfo(char* s)
{
    while (s[0]) {
        if (s[0] == '@') {
            int colors[] = {
                0, 0, cp_green, cp_cyan,
                cp_red, cp_magenta, cp_yellow, cp_white
            };
            attrset(COLOR_PAIR(colors[s[1] - '0']));
            s += 2;
        } else {
            addch(s[0]);
            s += 1;
        }
    }
}

//*******************************************************
// Start Display
//*******************************************************
void startdisplay()
{
    int i;

    // Clear the screen
    bkgd(CP(cp_white) | ' ');
    clear();

    // Draw to screen

    // ASM for some reason puts the same thing in the upper left corner again?

    move(1, 0);
    attrset(CP(cp_white_on_blue));
    addch(CurrentCPU + '0');
    for (i = 15; i; i--)
        addch(ACS_HLINE);
    printw(" CC:    Y:    ");
    for (i = 19; i; i--)
        addch(ACS_HLINE);
    addch(' ');
    for (i = 11; i; i--)
        addch(' ');
    addch(' ');
    for (i = 16; i; i--)
        addch(ACS_HLINE);
    addch(ACS_URCORNER);

    for (i = 2; i < 22; i++) {
        mvaddch(i, 0, ACS_VLINE);
        hline(' ', 77);
        mvaddch(i, 78, ACS_VLINE);
        mvaddch(i, 79, CHECK);
    }

    mvaddch(22, 0, ACS_LLCORNER);
    for (i = 77; i; i--)
        addch(ACS_HLINE);
    mvaddch(22, 78, ACS_LRCORNER);
    mvaddch(22, 79, CHECK);

    move(23, 1);
    for (i = 79; i; i--)
        addch(CHECK);

    // Print debugger information

    move(0, 2);
    attrset(CP(cp_white));
    printinfo("- @5Z@4S@3N@2E@6S@7 debugger -");

    move(1, 4);
    attrset(CP(cp_white_on_blue));
    printinfo(" 65816 ");

    // HACK ALERT! this should really be on the bottom line, but
    // xterms default to being one line shorter than 80x25, so this
    // won't be on the bottom line on DOS!
    // Also, we are printing on top of the (currently invisible) drop shadow!"
    move(23, 0);
    printinfo("@4(@6T@4)@7race for  @4(@6B@4)@7reakpoint  "
              "@4(@6Enter@4)@7 Next  "
              "@4(@6M@4)@7odify  @4(@6F9@4)@7 Signal  @4(@6F1@4)@7 Run");

    // ...
    move(0, 0);
    refresh();
}

//*******************************************************
// Next Opcode              Writes the next opcode & regs
//*******************************************************
// 008000 STZ $123456,x A:0000 X:0000 Y:0000 S:01FF DB:00 D:0000 P:33 E+

/*
void addtail() {
    debugt++;
    if (debugt == 100)
    debugt = 0;
    if (debugt == debugh)
    debugh++;
    if (debugh == 100)
    debugh = 0;
}
*/

// I'm going to have to completely rip out byuu's effective address
// stuff, it is just plain *WRONG*, besides being unsafe...

// For next time, http://www.zophar.net/tech/files/65c816.txt seems
// like a good reference for effective address calculation; also, use
// 24-bit addresses for all calculations (so completely rip out
// memr8, too). Also, preferably read memory in a
// non-destructive way.

// This whole instr[1] thing probably isn't quite right either, but it
// seems unlikely that instructions would be stored discontiguously
// than that data would span 64kb boundaries.

void out65816_addrmode(unsigned char* instr)
{
    char* padding = "";

#define GETXB() ((ocname[4 * instr[0]] != 'J') ? xdb : xpb)

#define INDEX_RIGHT(addr, index)                               \
    ((xp & 0x10)                                               \
            ? (((addr) & ~0xff) | (((addr) + (index)) & 0xff)) \
            : (((addr) & ~0xffff) | (((addr) + (index)) & 0xffff)))

    // each mode must output 19 characters
    switch (addrmode[instr[0]]) {

    case 0:
    case 6:
    case 21:
        // nothing to show

        wprintw(debugwin, "%19s", padding);
        break;

    case 1: // #$12,#$1234 (M-flag)
        wprintw(debugwin, "#$");
        if (xp & 0x20) {
            wprintw(debugwin, "%02x", instr[1]);
            wprintw(debugwin, "%15s", padding);
        } else {
            wprintw(debugwin, "%04x", *(unsigned short*)(instr + 1));
            wprintw(debugwin, "%13s", padding);
        }
        break;

    case 2: // $1234 : db+$1234
        wprintw(debugwin, "$%04x", *(unsigned short*)(instr + 1));
        wprintw(debugwin, "%5s[%02x%04x] ", padding, GETXB(),
            *(unsigned short*)(instr + 1));
        break;

    case 3: // $123456
        wprintw(debugwin, "$%02x%04x", instr[3], *(unsigned short*)(instr + 1));
        wprintw(debugwin, "%12s", padding);
        break;

    case 4: // $12 : $12+d
        wprintw(debugwin, "$%02x%7s[%02x%04x] ", instr[1], padding, 0,
            instr[1] + xd);
        break;

    case 5: // A
        wprintw(debugwin, "A%18s", padding);
        break;

    case 7: // ($12),y
    {
        wprintw(debugwin, "($%02x),Y   ", instr[1]);
        wprintw(debugwin, "[nnnnnn] ");
        break;
    }

    case 8: // [$12],y
    {
        unsigned short addr;
        unsigned int t;

        wprintw(debugwin, "[$%02x],Y   ", instr[1]);

        addr = instr[1] + xd;
        t = memr8(0, addr);
        t |= memr8(0, addr + 1) << 8;
        t |= memr8(0, addr + 2) << 16;
        t = INDEX_RIGHT(t, xy);
        wprintw(debugwin, "[%06x] ", t);

        break;
    }

    case 9: // ($12,x)
    {
        wprintw(debugwin, "($%02x,X)   ", instr[1]);
        wprintw(debugwin, "[nnnnnn] ");
        break;
    }

    case 10: // $12,x : $12+d+x
    {
        wprintw(debugwin, "$%02x,X%5s", instr[1], padding);
        wprintw(debugwin, "[%06x] ", INDEX_RIGHT(instr[1] + xd, xx));
        break;
    }

    case 11: // $12,y
    {
        wprintw(debugwin, "$%02x,Y%5s", instr[1], padding);
        wprintw(debugwin, "[%06x] ", INDEX_RIGHT(instr[1] + xd, xy));
        break;
    }

    case 12: // $1234,x : dbr+$1234+x
    {
        unsigned int t = instr[1] | (instr[2] << 8);
        wprintw(debugwin, "$%04x,X   ", t);
        t = INDEX_RIGHT(t, xx);
        wprintw(debugwin, "[%02x%04x] ", xdb, t);

        break;
    }

    case 13: // $1234,y : dbr+$1234+y
    {
        unsigned int t = instr[1] | (instr[2] << 8);
        wprintw(debugwin, "$%04x,Y   ", t);
        t = INDEX_RIGHT(t, xy);
        wprintw(debugwin, "[%02x%04x] ", xdb, t);

        break;
    }

    case 14: // $123456,x : $123456+x
    {
        unsigned int t = instr[1] | (instr[2] << 8) | (instr[3] << 16);
        wprintw(debugwin, "$%06x,X ", t);
        t = INDEX_RIGHT(t, xx);
        wprintw(debugwin, "[%06x] ", t);

        break;
    }

    case 15: // +-$12 / $1234
    {
        signed char c = instr[1];
        unsigned short t = c + xpc + 2;

        wprintw(debugwin, "$%04x%4s [%02x%04x] ", t, padding, xpb, t);

        break;
    }

    case 16: // +-$1234 / $1234
    {
        unsigned short s = instr[1] | (instr[2] << 8);
        unsigned short t = s + xpc + 3;

        wprintw(debugwin, "$%04x%4s [%02x%04x] ", t, padding, xpb, t);

        break;
    }

    case 17: // ($1234)
    {
        wprintw(debugwin, "($%04x)   ", instr[1]);
        wprintw(debugwin, "[nnnnnn] ");
        break;
    }

    case 18: // ($12)
    {
        unsigned short addr1, addr2;
        wprintw(debugwin, "($%02x)%5s", instr[1], padding);

        addr1 = instr[1] + xd;

        addr2 = memr8(00, addr1);
        addr2 |= memr8(00, addr1 + 1) << 8;

        wprintw(debugwin, "[%02x%04x] ", xdb, addr2);

        break;
    }

    case 19: // [$12]
    {
        wprintw(debugwin, "[$%02x]%5s", instr[1], padding);

        wprintw(debugwin, "[nnnnnn] ");

        break;
    }

    case 20: // ($1234,x)
    {
        unsigned short cx = *(unsigned short*)(instr + 1);
        unsigned short x;

        wprintw(debugwin, "($%04x,X) [%02x", cx, xpb);
        if (xp & 0x10)
            cx = (cx & 0xFF00) | ((cx + xx) & 0xFF);
        else
            cx += xx;
        // .out20n
        x = memr8(xpb, cx);
        x += memr8(xpb, cx + 1) << 8;
        wprintw(debugwin, "%04x] ", x);

        break;
    }

    case 22: // d,s
        wprintw(debugwin, "$%02x,S%5s", instr[1], padding);
        wprintw(debugwin, "[nnnnnn] ");
        break;

    case 23: // (d,s),y
        wprintw(debugwin, "($%02x,S),Y ", instr[1]);
        wprintw(debugwin, "[nnnnnn] ");
        break;

    case 24: // xyc - $1234
        wprintw(debugwin, "$%02x%02x%14s", instr[2], instr[1], padding);
        break;

    case 25: // #$12 (Flag Operations)
        wprintw(debugwin, "#$%02x%15s", instr[1], padding);
        break;

    case 26: // #$12,#$1234 (X-flag)
        if (xp & 0x10) {
            wprintw(debugwin, "#$%02x%15s", instr[1], padding);
        } else {
            wprintw(debugwin, "#$%04x%13s",
                *(unsigned short*)(instr + 1), padding);
        }
        break;

    case 27: // [$1234]
        wprintw(debugwin, "[$%02x%02x]    ", instr[2], instr[1]);
        break;

    default:
        wprintw(debugwin, "%15s %02d ", "bad addr mode", addrmode[instr[0]]);
    }
}

unsigned char* findoppage()
{
    if (xpc & 0x8000) {
        return snesmmap[xpb];
    } else {
        // lower address
        if ((xpc < 0x4300) || (memtabler8[xpb] != regaccessbankr8)) {
            // lower memory
            return snesmap2[xpb];
        } else {
            // dma
            return (u1*)dmadata - 0x4300; // XXX ugly cast
        }
    }
}

unsigned char* findop()
{
    unsigned char* address = findoppage() + xpc;
    return address;
}

// print out a 65816 instruction
void out65816()
{
    unsigned char *address, opcode;
    char opname[5] = "FOO ";

    wprintw(debugwin, "%02x%04x ", xpb, xpc);

    // is this safe?
    address = findop();
    opcode = *address;

    memcpy(opname, &ocname[opcode * 4], 4);
    wprintw(debugwin, "%s", opname);

    out65816_addrmode(address);

    wprintw(debugwin, "A:%04x X:%04x Y:%04x S:%04x DB:%02x D:%04x P:%02x %c",
        xa, xx, xy, xs, xdb, xd, xp, (xe == 1) ? 'E' : 'e');
}

void outsa1()
{
    // stub!
}

void nextopcode()
{
    attrset(CP(cp_white_on_blue));

    move(1, 50);
    printw("%11d", numinst);
    move(1, 20);
    printw("%3d", curcyc);
    move(1, 26);
    printw("%3d", curypos);

    // I don't understand the buffering scheme here... I'm just going
    // to hope it isn't really all that important.

    // if (debugsa1 != 1)
    out65816();
    // else
    //   outputbuffersa1();
}

void cleardisplay()
{
    move(0, 0); /* clear(); refresh(); */
    endwin();
}

//*******************************************************
// Next SPC Opcode          Writes the next opcode & regs
//*******************************************************
// 008000 STZ $123456,x A:0000 X:0000 Y:0000 S:01FF DB:00 D:0000 P:33 E+

void outspc_addrmode()
{
    unsigned char mode;
    char* format;
    char buf[16] = "               ";
    char* p;

#define HEX8(val)                     \
    do {                              \
        p += sprintf(p, "%02x", val); \
        *p = ' ';                     \
    } while (0)
#define HEX16(val)                    \
    do {                              \
        p += sprintf(p, "%04x", val); \
        *p = ' ';                     \
    } while (0)

    mode = ArgumentTable[spcPCRam[0]];
    format = AddressTable[mode];

    // memset(buf, ' ', 15); buf[15] = 0;

    p = buf;
    while (*format) {
        if (*format != '%') {
            *p++ = *format++;
            continue;
        }

        format++;
        switch (*format++) {

        case '1': // first byte
            HEX8(spcPCRam[1]);
            break;

        case '2': // second byte
            HEX8(spcPCRam[2]);
            break;

        case '3': // hinib
            *p++ = (spcPCRam[0] >> 4) + '0';
            *p++ = ' ';
            break;

        case '4': // hinib2
            *p++ = (spcPCRam[0] >> 5) + '0';
            *p++ = ' ';
            break;

        case '5': // rela2pc2
        {
            signed char off;
            off = *(signed char*)(spcPCRam + 1);
            HEX16(off + 2 + (spcPCRam - SPCRAM));
            // format += 3;
            break;
        }

        case '6': // dp
        {
            *p++ = '$';

            if (spcP & 0x20) {
                *p++ = '1';
            } else {
                *p++ = '0';
            }

            break;
        }

        case '8': // memorybit
            HEX16((*(unsigned short*)(spcPCRam + 1)) >> 3);
            // format += 2;
            break;

        case '9': // memorybitlow
            *p++ = ',';
            *p++ = (spcPCRam[1] & 0x7) + '0';
            break;

        case 'A': // rela2pc1
        {
            signed char off;
            off = *(signed char*)(spcPCRam + 1);
            HEX16(off + 2 + spcPCRam - SPCRAM);
            // format += 2;
            break;
        }

        case 'B': // rela2pc2at2
        {
            signed char off;
            off = *(signed char*)(spcPCRam + 2);
            HEX16(off + 2 + spcPCRam - SPCRAM);
            // format += 2;
            break;
        }
        }
    }

    buf[15] = 0;
    waddstr(debugwin, buf);
}

void nextspcopcode()
{
    if (!soundon)
        return;
    if (cycpbl >= 55)
        return;

    // output spc pc & opcode #
    wprintw(debugwin, " %04x/%02x ", spcPCRam - SPCRAM, spcPCRam[0]);

    // output instruction
    wprintw(debugwin, "%-6s", spcnametab[spcPCRam[0]]);
    outspc_addrmode();

    // output registers
    wprintw(debugwin, "A:%02x X:%02x Y:%02x S:%02x ", spcA, spcX, spcY, spcS);
    wprintw(debugwin, "N%cO%cD%c?%cH%cI%cZ%cC%c",
        (spcNZ & 0x80) ? '+' : '-',
        (spcP & 0x40) ? '+' : '-',
        (spcP & 0x20) ? '+' : '-',
        (spcP & 0x10) ? '+' : '-',
        (spcP & 0x08) ? '+' : '-',
        (spcP & 0x04) ? '+' : '-',
        (spcP & 0x02) ? '+' : '-',
        (spcP & 0x01) ? '+' : '-');

    wprintw(debugwin, "\n");
}

//*******************************************************
// Debugger OpCode Information
//*******************************************************

// Yes, I know, not very C style, but I really really really didn't
// want to type all those quote marks and commas. --SamB
char* ocname = "BRK ORA COP ORA TSB ORA ASL ORA PHP ORA ASL PHD TSB ORA ASL ORA "
               "BPL ORA ORA ORA TRB ORA ASL ORA CLC ORA INC TCS TRB ORA ASL ORA "
               "JSR AND JSL AND BIT AND ROL AND PLP AND ROL PLD BIT AND ROL AND "
               "BMI AND AND AND BIT AND ROL AND SEC AND DEC TSC BIT AND ROL AND "
               "RTI EOR WDM EOR MVP EOR LSR EOR PHA EOR LSR PHK JMP EOR LSR EOR "
               "BVC EOR EOR EOR MVN EOR LSR EOR CLI EOR PHY TCD JMP EOR LSR EOR "
               "RTS ADC PER ADC STZ ADC ROR ADC PLA ADC ROR RTL JMP ADC ROR ADC "
               "BVS ADC ADC ADC STZ ADC ROR ADC SEI ADC PLY TDC JMP ADC ROR ADC "
               "BRA STA BRL STA STY STA STX STA DEY BIT TXA PHB STY STA STX STA "
               "BCC STA STA STA STY STA STX STA TYA STA TXS TXY STZ STA STZ STA "
               "LDY LDA LDX LDA LDY LDA LDX LDA TAY LDA TAX PLB LDY LDA LDX LDA "
               "BCS LDA LDA LDA LDY LDA LDX LDA CLV LDA TSX TYX LDY LDA LDX LDA "
               "CPY CMP REP CMP CPY CMP DEC CMP INY CMP DEX WAI CPY CMP DEC CMP "
               "BNE CMP CMP CMP PEI CMP DEC CMP CLD CMP PHX STP JML CMP DEC CMP "
               "CPX SBC SEP SBC CPX SBC INC SBC INX SBC NOP XBA CPX SBC INC SBC "
               "BEQ SBC SBC SBC PEA SBC INC SBC SED SBC PLX XCE JSR SBC INC SBC ";

// Immediate Addressing Modes :
//   09 - ORA-M, 29 - AND-M, 49 - EOR-M, 69 - ADC-M, 89 - BIT-M,
//   A0 - LDY-X, A2 - LDX-X, A9 - LDA-M, C0 - CPY-X, C2 - REP-B,
//   C9 - CMP-M, E0 - CPX-X, E2 - SEP-B, E9 - SBC-M
//   Extra Addressing Mode Values : B(1-byte only) = 25, X(by X flag) = 26

unsigned char addrmode[256] = {
    25, 9, 25, 22, 4, 4, 4, 19, 21, 1, 5, 21, 2, 2, 2, 3,
    15, 7, 18, 23, 4, 10, 10, 8, 6, 13, 5, 6, 2, 12, 12, 14,
    2, 9, 3, 22, 4, 4, 4, 19, 21, 1, 5, 21, 2, 2, 2, 3,
    15, 7, 18, 23, 10, 10, 10, 8, 6, 13, 5, 6, 12, 12, 12, 14,
    21, 9, 0, 22, 24, 4, 4, 19, 21, 1, 5, 21, 2, 2, 2, 3,
    15, 7, 18, 23, 24, 10, 10, 8, 6, 13, 21, 6, 3, 12, 12, 14,
    21, 9, 2, 22, 4, 4, 4, 19, 21, 1, 5, 21, 17, 2, 2, 3,
    15, 7, 18, 23, 10, 10, 10, 8, 6, 13, 21, 6, 20, 12, 12, 14,
    15, 9, 16, 22, 4, 4, 4, 19, 6, 1, 6, 21, 2, 2, 2, 3,
    15, 7, 18, 23, 10, 10, 11, 8, 6, 13, 6, 6, 2, 12, 12, 14,
    26, 9, 26, 22, 4, 4, 4, 19, 6, 1, 6, 21, 2, 2, 2, 3,
    15, 7, 18, 23, 10, 10, 11, 8, 6, 13, 6, 6, 12, 12, 13, 14,
    26, 9, 25, 22, 4, 4, 4, 19, 6, 1, 6, 6, 2, 2, 2, 3,
    15, 7, 18, 23, 18, 10, 10, 8, 6, 13, 21, 6, 27, 12, 12, 14,
    26, 9, 25, 22, 4, 4, 4, 19, 6, 1, 6, 6, 2, 2, 2, 3,
    15, 7, 18, 23, 2, 10, 10, 8, 6, 13, 21, 6, 20, 12, 12, 14
};

char* spcnametab[256] = {
    "NOP", "TCALL", "SET1", "BBS",
    "OR", "OR", "OR", "OR",
    "OR", "OR", "OR1", "ASL",
    "ASL", "PUSH", "TSET1", "BRK",

    "BPL", "TCALL", "CLR1", "BBC",
    "OR", "OR", "OR", "OR",
    "OR", "OR", "DECW", "ASL",
    "ASL", "DEC", "CMP", "JMP",

    "CLRP", "TCALL", "SET1", "BBS",
    "AND", "AND", "AND", "AND",
    "AND", "AND", "OR1", "ROL",
    "ROL", "PUSH", "CBNE", "BRA",

    "BMI", "TCALL", "CLR1", "BBC",
    "AND", "AND", "AND", "AND",
    "AND", "AND", "INCW", "ROL",
    "ROL", "INC", "CMP", "CALL",

    "SETP", "TCALL", "SET1", "BBS",
    "EOR", "EOR", "EOR", "EOR",
    "EOR", "EOR", "AND1", "LSR",
    "LSR", "PUSH", "TCLR1", "PCALL",

    "BVC", "TCALL", "CLR1", "BBC",
    "EOR", "EOR", "EOR", "EOR",
    "EOR", "EOR", "CMPW", "LSR",
    "LSR", "MOV", "CMP", "JMP",

    "CLRC", "TCALL", "SET1", "BBS",
    "CMP", "CMP", "CMP", "CMP",
    "CMP", "CMP", "AND1", "ROR",
    "ROR", "PUSH", "DMNZ", "RET",

    "BVS", "TCALL", "CLR1", "BBC",
    "CMP", "CMP", "CMP", "CMP",
    "CMP", "CMP", "ADDW", "ROR",
    "ROR", "MOV", "CMP", "RET1",

    "SETC", "TCALL", "SET1", "BBS",
    "ADC", "ADC", "ADC", "ADC",
    "ADC", "ADC", "EOR1", "DEC",
    "DEC", "MOV", "POP", "MOV",

    "BCC", "TCALL", "CLR1", "BBC",
    "ADC", "ADC", "ADC", "ADC",
    "ADC", "ADC", "SUBW", "DEC",
    "DEC", "MOV", "DIV", "XCN",

    "EI", "TCALL", "SET1", "BBS",
    "SBC", "SBC", "SBC", "SBC",
    "SBC", "SBC", "MOV1", "INC",
    "INC", "CMP", "POP", "MOV",

    "BCS", "TCALL", "CLR1", "BBC",
    "SBC", "SBC", "SBC", "SBC",
    "SBC", "SBC", "MOVW", "INC",
    "INC", "MOV", "DAS", "MOV",

    "DI", "TCALL", "SET1", "BBS",
    "MOV", "MOV", "MOV", "MOV",
    "CMP", "MOV", "MOV1", "MOV",
    "MOV", "MOV", "POP", "MUL",

    "BNE", "TCALL", "CLR1", "BBC",
    "MOV", "MOV", "MOV", "MOV",
    "MOV", "MOV", "MOVW", "MOV",
    "DEC", "MOV", "CBNE", "DAA",

    "CLRV", "TCALL", "SET1", "BBS",
    "MOV", "MOV", "MOV", "MOV",
    "MOV", "MOV", "NOT1", "MOV",
    "MOV", "NOTC", "POP", "SLEEP",

    "BEQ", "TCALL", "CLR1", "BBC",
    "MOV", "MOV", "MOV", "MOV",
    "MOV", "MOV", "MOV", "MOV",
    "INC", "MOV", "DBNZ", "STOP"
};
// need... air... badly...!

// %1 = Byte, %2 = Second Byte, %3 = high nibble of opcode #,
// %4 = high nibble of opcode # and 07h, %5 = relative to PC+2
// %6 = dp ($0/$1)
// %7 = memory SHR 3 Low, %8 = memory SHR 3 High, %9 = ,memory AND 7h
// %A = relative to PC+1, %B = relative to PC+1 at second byte
char* AddressTable[68] = {
    //                  1                  1                  1
    "", "%3", "%6%1,%4", "B%4 %6%1,$%B+1",
    // 0 : nothing
    // 1 : the high nibble
    // 2 : the high nibble first 3 bit (and 0111000 then shift)
    // 3 : 2 + relative
    "A,%6%1", "A,$%2%1", "A,(X)", "A,(%6%1+x)",
    // 4 : A,dp
    // 5 : A,labs
    // 6 : A,(X)
    // 7 : A,(dp+X)
    "A,#$%1", "(%6%2),(%6%1)", "CF,mbit%8%7%9", "%6%1",
    // 8 : A,#inm
    // 9 : dp(d),dp(s)   (two dp)
    // 10 : Carry flag, memory bit          (can only access from 0 to 1fff)
    // 11 : dp
    "$%2%1", "PSW", "$%A", "A,%6%1+X",
    // 12 : labs
    // 13 : PSW
    // 14 : rel
    // 15 : A,dp+X
    "A,$%2%1+X", "A,$%2%1+Y", "A,(%6%1)+Y", "%6%2,#$%1",
    // 16 : A,labs+X
    // 17 : A,labs+Y
    // 18 : A,(dp)+Y
    // 19 : dp,#inm
    "(X),(Y)", "%6%1+X", "A", "X",
    // 20 : (X),(Y)
    // 21 : dp+X
    // 22 : A
    // 23 : X
    "X,%2%1", "($%2%1+X)", "CF,/(mb%8%7%9)", "%6%1",
    // 24 : X,labs
    // 25 : (labs+X)
    // 26 : C,/mem.bit
    // 27 : upage         (same as dp but for a call)
    "YA,%6%1", "X,A", "Y,$%2%1", "Y",
    // 28 : YA,dp
    // 29 : X,A
    // 30 : Y,labs
    // 31 : Y
    "Y,%6%1", "Y,#$%1", "%6%1,$%B", "X,%6%1",
    // 32 : Y,dp
    // 33 : Y,#inm
    // 34 : dp,rel
    // 35 : X,dp
    "A,X", "%6%2,#$%1", "X,SP", "YA,X",
    // 36 : A,X
    // 37 : dp,#inm
    // 38 : X,SP
    // 39 : YA,X
    "(X)+,A", "SP,X", "A,(X)+", "%6%1,A",
    // 40 : (X)+,A
    // 41 : SP,X
    // 42 : A,(X)+
    // 43 : dp,A
    "$%2%1,A", "(X),A", "%6%1+X,A", "X,#$%1",
    // 44 : labs,A
    // 45 : (X),A
    // 46 : (dp+X),A
    // 47 : X,#inm
    "$%2%1,X", "mb%8%7%9,CF", "%6%1,Y", "$%2%1,Y",
    // 48 : labs,X
    // 49 : mem.bit,C
    // 50 : dp,Y
    // 51 : labs,Y
    "YA", "%6%1+X,A", "$%2%1+X,A", "$%2%1+Y,A",
    // 52 : YA
    // 53 : dp+X,A
    // 54 : labs+X,A
    // 55 : labs+Y,A
    "(%6%1)+Y,A", "%6%1,X", "%6%1+Y,X", "%6%1,YA",
    // 56 : (dp)+Y,A
    // 57 : dp,X
    // 58 : dp+Y,X
    // 59 : dp,YA
    "%6%1+X,Y", "A,Y", "%6%2+X,$%A", "mb%8%7%9,CF",
    // 60 : dp+X,Y
    // 61 : A,Y
    // 62 : dp+X,rel
    // 63 : mem.bit
    "X,%6%1+Y", "Y,%6%1+X", "Y,A", "Y,$%A",
    // 64 : X,dp+Y
    // 65 : Y,dp+X
    // 66 : Y,A
    // 67 : Y,rel

};

unsigned char ArgumentTable[256] = {
    //     00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 12, 0,
    //     10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 11, 21, 22, 23, 24, 25,
    //     20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 26, 11, 12, 22, 34, 14,
    //     30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 11, 21, 22, 23, 35, 12,
    //     40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 23, 12, 27,
    //     50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 28, 21, 22, 29, 30, 12,
    //     60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 26, 11, 12, 31, 34, 0,
    //     70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 28, 21, 22, 36, 32, 0,
    //     80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 33, 13, 37,
    //     90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 28, 21, 22, 38, 39, 22,
    //     A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 33, 22, 40,
    //     B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF
    14, 1, 2, 3, 15, 16, 17, 18, 19, 20, 28, 21, 22, 41, 22, 42,
    //     C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
    0, 1, 2, 3, 43, 44, 45, 46, 47, 48, 49, 50, 51, 47, 23, 52,
    //     D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF
    14, 1, 2, 3, 53, 54, 55, 56, 57, 58, 59, 60, 31, 61, 62, 22,
    //     E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF
    0, 1, 2, 3, 4, 5, 6, 7, 8, 24, 63, 32, 30, 0, 31, 0,
    //     F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF
    14, 1, 2, 3, 15, 16, 17, 18, 35, 64, 9, 65, 31, 66, 67, 0
};

// Jonas Quinn's file functions

void SaveOAMRamLog()
{
    FILE* fp = 0;

    if ((fp = fopen_dir(ZCfgPath, "vram.dat", "wb"))) {
        fwrite(oamram, 1, 544, fp);
        fclose(fp);
    }
}

void debugdump()
{
    FILE* fp = 0;

    if ((fp = fopen_dir(ZCfgPath, "SPCRAM.dmp", "wb"))) {
        fwrite(SPCRAM, 1, 65536, fp);
        fclose(fp);
    }

    if ((fp = fopen_dir(ZCfgPath, "DSP.dmp", "wb"))) {
        fwrite(DSPMem, 1, 256, fp);
        fclose(fp);
    }
}
