Breakpoint support in the GUI
authorslack <slack@codemaniacs.com>
Sun, 26 Apr 2009 00:59:25 +0000 (02:59 +0200)
committerslack <slack@codemaniacs.com>
Sun, 26 Apr 2009 00:59:25 +0000 (02:59 +0200)
core/GameBoy.h
qtboi/QtBoiDisassemblyWindow.cc
qtboi/QtBoiEmuThread.cc
qtboi/QtBoiEmuThread.h
qtboi/QtBoiMainWindow.cc
qtboi/QtBoiStatusWindow.cc

index cef00d51cc2bd82dc409c8a0ec905b47a22f0d18..a330fe1daa6452278a58ae3ebf2aa3f5907bf735 100644 (file)
@@ -68,6 +68,17 @@ class GameBoy
                        IRQ_JOYPAD   = 0x10,
                };
 
+               struct Breakpoint {
+                       int addr;
+                       bool enabled;
+
+                       Breakpoint(int a, bool e): addr(a), enabled(e) {}
+                       Breakpoint(): addr(-1), enabled(false) {}
+               };
+
+               typedef std::map<int, Breakpoint> BreakpointMap;
+               
+
                // Constructors / destructors
                GameBoy(GameBoyType type=GAMEBOY);
                ~GameBoy();
@@ -92,6 +103,7 @@ class GameBoy
                void delete_breakpoint (int id);
                void enable_breakpoint (int id);
                void disable_breakpoint(int id);
+               const BreakpointMap get_breakpoints() const { return breakpoints; }
 
                std::string status_string();
                Instruction disassemble_opcode(u16 addr);
@@ -178,17 +190,6 @@ class GameBoy
                // free ROM (used in destructor and load_rom)
                void free_ROM();
                
-               // debug things
-               struct Breakpoint {
-                       int addr;
-                       bool enabled;
-
-                       Breakpoint(int a, bool e): addr(a), enabled(e) {}
-                       Breakpoint(): addr(-1), enabled(false) {}
-               };
-
-               typedef std::map<int, Breakpoint> BreakpointMap;
-               
                BreakpointMap breakpoints;
                int last_breakpoint_id;
 
index b0983da4e4ce22c69af06a9a50959458f76cad4f..9827e717e4dc6cbc76251e20194c93174ab8ac6e 100644 (file)
 #include "../common/toString.h"
 
 QtBoiDisassemblyWindow::QtBoiDisassemblyWindow(QWidget *parent, GameBoy *gb, QHash<u32,QString> *tags)
-        :QWidget(parent), gb(gb), tags(tags), history(), historyPosition(-1)
+:QWidget(parent), gb(gb), tags(tags), history(), historyPosition(-1)
 {
-       setFocusPolicy(Qt::NoFocus);
-
-       browser = new QTextBrowser(this);
-        browser->setOpenLinks(false);
-       browser->setFont(QFont("courier"));
-        browser->setFocusPolicy(Qt::NoFocus);
-        browser->setMinimumSize(500,500);
-       connect(browser, SIGNAL(anchorClicked(const QUrl&)), this, SIGNAL(anchorClicked(const QUrl &)));
-
-
-       backButton    = new QPushButton("Back", this);
-       backButton->setEnabled(false);
-        backButton->setFocusPolicy(Qt::NoFocus);
-       forwardButton = new QPushButton("Forward", this);
-       forwardButton->setEnabled(false);
-        forwardButton->setFocusPolicy(Qt::NoFocus);
-       connect(backButton, SIGNAL(clicked()), this, SLOT(historyBack()));
-       connect(forwardButton, SIGNAL(clicked()), this, SLOT(historyForward()));
-       
-       //backButton->setIcon(QIcon("../icons/go-next.svg"));
-       //forwardButton->setIcon(QIcon("../icons/go-previous.svg")); 
-       
-       QHBoxLayout *buttons = new QHBoxLayout();
-       buttons->addWidget(backButton);
-       buttons->addWidget(forwardButton);
-
-       QVBoxLayout *vbox = new QVBoxLayout;
-       vbox->addWidget(browser);
-       vbox->addLayout(buttons);
-
-       setLayout(vbox);
+    setFocusPolicy(Qt::NoFocus);
+
+    browser = new QTextBrowser(this);
+    browser->setOpenLinks(false);
+    browser->setFont(QFont("courier"));
+    browser->setFocusPolicy(Qt::NoFocus);
+    browser->setMinimumSize(500,500);
+    connect(browser, SIGNAL(anchorClicked(const QUrl&)), this, SIGNAL(anchorClicked(const QUrl &)));
+
+
+    backButton    = new QPushButton("Back", this);
+    backButton->setEnabled(false);
+    backButton->setFocusPolicy(Qt::NoFocus);
+    forwardButton = new QPushButton("Forward", this);
+    forwardButton->setEnabled(false);
+    forwardButton->setFocusPolicy(Qt::NoFocus);
+    connect(backButton, SIGNAL(clicked()), this, SLOT(historyBack()));
+    connect(forwardButton, SIGNAL(clicked()), this, SLOT(historyForward()));
+
+    //backButton->setIcon(QIcon("../icons/go-next.svg"));
+    //forwardButton->setIcon(QIcon("../icons/go-previous.svg")); 
+
+    QHBoxLayout *buttons = new QHBoxLayout();
+    buttons->addWidget(backButton);
+    buttons->addWidget(forwardButton);
+
+    QVBoxLayout *vbox = new QVBoxLayout;
+    vbox->addWidget(browser);
+    vbox->addLayout(buttons);
+
+    setLayout(vbox);
 }
 
 QtBoiDisassemblyWindow::~QtBoiDisassemblyWindow()
@@ -52,161 +52,181 @@ QtBoiDisassemblyWindow::~QtBoiDisassemblyWindow()
 
 std::string QtBoiDisassemblyWindow::htmlLinkMem(u32 addr)
 {
-       std::string result("<a href=\"gotoaddr:");
-       result += toString(addr);
-       result += "\">";
-       if (tags->value(addr) != "")
-               result += tags->value(addr).toStdString();
-       else
-               result += toStringHex(addr, 4);
-       result += "</a>";
-
-       return result;
+    std::string result("<a href=\"gotoaddr:");
+    result += toString(addr);
+    result += "\">";
+    if (tags->value(addr) != "")
+        result += tags->value(addr).toStdString();
+    else
+        result += toStringHex(addr, 4);
+    result += "</a>";
+
+    return result;
 }
 
 std::string QtBoiDisassemblyWindow::operandToHtml(const Instruction::Operand &op)
 {
-       std::string result;
-       switch(op.type)  
-       {
-               case Instruction::MEM_DIRECT:
-               result += "(";
-               result += htmlLinkMem(op.val);
-               result += ")";
-               break;
-
-               case Instruction::INM16:
-               result += toStringHex(op.val,4);
-               break;
-
-               default:
-               return op.str;
-       }
-
-       return result;
+    std::string result;
+    switch(op.type)  
+    {
+    case Instruction::MEM_DIRECT:
+        result += "(";
+        result += htmlLinkMem(op.val);
+        result += ")";
+        break;
+
+    case Instruction::INM16:
+        result += toStringHex(op.val,4);
+        break;
+
+    default:
+        return op.str;
+    }
+
+    return result;
 }
 
 
 std::string QtBoiDisassemblyWindow::insToHtml(const Instruction &ins)
 {
 
-       std::string result;
-
-       if (ins.type == Instruction::RESET)
-       {
-               result += "RST ";
-               result += htmlLinkMem(strtol(ins.str.substr(4).c_str(), 0, 16));
-       }
-       else
-       {
-               result += ins.str;
-               result += " ";
-               if (ins.str.substr(0,2)=="JR")
-               {
-                       result += operandToHtml(ins.op1);
-                       result += " [";
-                       result += htmlLinkMem(ins.op2.val);
-                       result += "]";
-               }
-               else if ((ins.str.substr(0,2)=="JP" && ins.op1.type == Instruction::INM16)
-                               || ins.str.substr(0,4)=="CALL")
-               {
-                       result += htmlLinkMem(ins.op1.val);
-               }
-               else
-               {
-                       result += operandToHtml(ins.op1);
-                       if (ins.op2.type != Instruction::NONE)
-                       {
-                               result += ", ";
-                               result += operandToHtml(ins.op2);
-                       }
-               }
-       }
-       return result;
+    std::string result;
+
+    if (ins.type == Instruction::RESET)
+    {
+        result += "RST ";
+        result += htmlLinkMem(strtol(ins.str.substr(4).c_str(), 0, 16));
+    }
+    else
+    {
+        result += ins.str;
+        result += " ";
+        if (ins.str.substr(0,2)=="JR")
+        {
+            result += operandToHtml(ins.op1);
+            result += " [";
+            result += htmlLinkMem(ins.op2.val);
+            result += "]";
+        }
+        else if ((ins.str.substr(0,2)=="JP" && ins.op1.type == Instruction::INM16)
+            || ins.str.substr(0,4)=="CALL")
+        {
+            result += htmlLinkMem(ins.op1.val);
+        }
+        else
+        {
+            result += operandToHtml(ins.op1);
+            if (ins.op2.type != Instruction::NONE)
+            {
+                result += ", ";
+                result += operandToHtml(ins.op2);
+            }
+        }
+    }
+    if (ins.str.substr(0,3)=="LDH") {
+        int port;
+        if (ins.op1.type == Instruction::MEM_DIRECT)
+            port = ins.op1.val - 0xFF00;
+        else
+            port = ins.op2.val - 0xFF00;
+        result += "  ["+GameBoy::get_port_name(port)+"]";
+    }
+    return result;
 }
 
 void QtBoiDisassemblyWindow::gotoAddress(u16 addr)
 {
-        int start = addr;
-        int end   = start+200;
-        int pos   = start;
+    GameBoy::BreakpointMap bpmap = gb->get_breakpoints();
 
-        if (end > 0xFFFF) end = 0xFFFF;
-        std::ostringstream str;
-        
-        str << "<html><head><title>Disassembly</title></head><body>";
-        str << "<table cellpadding=0 cellspacing=0>";
-        str << "<tr bgcolor=#c0ffc0><td width=25%>Labels</td><td width=15%>Addr</td><td width=15%>Opcodes</td><td>Instruction</td></tr>";
+    int start = addr;
+    int end   = start+200;
+    int pos   = start;
 
-        bool hilightBG=true;
+    if (end > 0xFFFF) end = 0xFFFF;
+    std::ostringstream str;
 
-        while (pos < end)
-        {
-                Instruction ins(gb->disassemble_opcode(pos));
-                str << "<tr bgcolor=" << (pos == gb->regs.PC ? "#ffc0c0" : (hilightBG ? "#d0d0d0" : "#ffffff")) << ">" <<
-                       "<td>" << tags->value(pos).toStdString() << "</td>" <<
-                       "<td><a href=\"newtag:" << std::dec << pos << "\">0x" << std::hex << std::setw(4) << std::setfill('0') << 
-                        pos << "</a></td><td>";
-                for (int i=0; i<ins.length; i++)
-                        str << std::setw(2) << int(gb->memory.read(pos+i)) << " ";
+    str << "<html><head><title>Disassembly</title></head><body>";
+    str << "<table cellpadding=0 cellspacing=0>";
+    str << "<tr bgcolor=#c0ffc0><td width=25%>Labels</td><td>&nbsp;BP&nbsp;</td><td width=15%>Addr</td><td width=15%>Opcodes</td><td>Instruction</td></tr>";
 
-                str << "</td><td>";
+    bool hilightBG=true;
 
-                str << insToHtml(ins) << "</td></tr>";
-                pos += ins.length;
+    while (pos < end)
+    {
+        bool isBP=false;
 
-                hilightBG = !hilightBG;
+        for (GameBoy::BreakpointMap::iterator i = bpmap.begin(); i != bpmap.end(); i++) {
+            if (i->second.addr == pos) {
+                isBP=true;
+                break;
+            }
         }
 
-        str << "</table></body></html>";
-
-        browser->setHtml(QString(str.str().c_str()));
-       currentAddress=addr;
-
-       // If there are "forward" history elements
-       // - check if we are going forwards or backwards
-       // - else, delete forward history and insert the new address
-       if (historyPosition < history.size()-1 && history[historyPosition+1] == addr)
-               historyPosition++;
-       else if (historyPosition > 0 && history[historyPosition-1] == addr)
-               historyPosition--;
-       else
-       {
-               while (history.size() > historyPosition+1)
-                       history.pop_back();
-               history.push_back(addr);
-               historyPosition++;
-       }
-
-       // enable/disable navigation buttons
-       if (historyPosition >= 1) backButton->setEnabled(true);
-       else backButton->setEnabled(false);
-
-       if (historyPosition >= 0 && historyPosition < history.size()-1) forwardButton->setEnabled(true);
-       else forwardButton->setEnabled(false);
+        Instruction ins(gb->disassemble_opcode(pos));
+        str << "<tr bgcolor=" << (isBP? "#ff0000" : (pos == gb->regs.PC ? "#ffc0c0" : (hilightBG ? "#d0d0d0" : "#ffffff"))) << ">" <<
+            "<td>" << tags->value(pos).toStdString() << "</td>" <<
+            "<td>&nbsp;<a href=\"togglebp:" << std::dec << pos << "\">"<< (isBP? "#" : "&nbsp;") <<"</a>&nbsp;</font></td>" <<
+            "<td><a href=\"newtag:" << std::dec << pos << "\">0x" << std::hex << std::setw(4) << std::setfill('0') << 
+            pos << "</a></td><td>";
+        for (int i=0; i<ins.length; i++)
+            str << std::setw(2) << int(gb->memory.read(pos+i)) << " ";
+
+        str << "</td><td>";
+
+        str << insToHtml(ins) << "</td></tr>";
+        pos += ins.length;
+
+        hilightBG = !hilightBG;
+    }
+
+    str << "</table></body></html>";
+
+    browser->setHtml(QString(str.str().c_str()));
+    currentAddress=addr;
+
+    // If there are "forward" history elements
+    // - check if we are going forwards or backwards
+    // - else, delete forward history and insert the new address
+    if (historyPosition < history.size()-1 && history[historyPosition+1] == addr)
+        historyPosition++;
+    else if (historyPosition > 0 && history[historyPosition-1] == addr)
+        historyPosition--;
+    else
+    {
+        while (history.size() > historyPosition+1)
+            history.pop_back();
+        history.push_back(addr);
+        historyPosition++;
+    }
+
+    // enable/disable navigation buttons
+    if (historyPosition >= 1) backButton->setEnabled(true);
+    else backButton->setEnabled(false);
+
+    if (historyPosition >= 0 && historyPosition < history.size()-1) forwardButton->setEnabled(true);
+    else forwardButton->setEnabled(false);
 }
 
 void QtBoiDisassemblyWindow::gotoPC()
 {
-        gotoAddress(gb->regs.PC);
+    gotoAddress(gb->regs.PC);
 }
 
 void QtBoiDisassemblyWindow::refresh()
 {
-        gotoAddress(currentAddress);
+    gotoAddress(currentAddress);
 }
 
 void QtBoiDisassemblyWindow::historyBack()
 {
-       if (historyPosition >= 1)
-               gotoAddress(history[historyPosition-1]);
+    if (historyPosition >= 1)
+        gotoAddress(history[historyPosition-1]);
 }
 
 void QtBoiDisassemblyWindow::historyForward()
 {
-       if (historyPosition < history.size()-1)
-               gotoAddress(history[historyPosition+1]);
+    if (historyPosition < history.size()-1)
+        gotoAddress(history[historyPosition+1]);
 }
 
 
index 29b46738ed8f56046ec56cb7e98a2cecd2f6c2b7..edcfa578080cc84ec244f3e07e840b581e1850a1 100644 (file)
@@ -139,9 +139,13 @@ void QtBoiEmuThread::run()
                                                {
                                                        frameCount = gb.video.get_frames_rendered();
                                                        emit redraw(gb.video.get_screen_buffer());
-                                                       msleep(5);
+                                                       //msleep(5);
                                                }
                                        }
+                    if (status == GameBoy::BREAKPOINT) {
+                        emit breakpointReached(gb.regs.PC);
+                        isPaused=true;
+                    }
                                        break;
                                case STEP:
                                        do
index e8e8b04e50c63c4d985ed45ec1227b380e8008df..41082113502ec82e06492b6bd78329544587a604 100644 (file)
@@ -33,6 +33,7 @@ class QtBoiEmuThread: public QThread
   
   signals:
     void emulationPaused();
+       void breakpointReached(uint addr);
     void redraw(const uchar *buffer);
 
   private:
index d5d6425371a951f6eacd9bdd690e72e7a7ba81d1..1b66baf21e859049c22008a8188d13b2425fac25 100644 (file)
@@ -220,6 +220,24 @@ void QtBoiMainWindow::onDisassemblyAnchorClicked(const QUrl& url)
                tags[addr] = tag;
                disassembly->refresh();
        }
+    else if (url.scheme() == "togglebp") {
+        u32 addr = url.path().toUInt();
+        bool bpFound = false;
+        GameBoy::BreakpointMap bpmap = emuThread->gb.get_breakpoints();
+        for (GameBoy::BreakpointMap::iterator i=bpmap.begin(); i != bpmap.end(); i++) {
+            if (i->second.addr == addr) {
+                emuThread->gb.delete_breakpoint(i->first);
+                bpFound = true;
+                break;
+            }
+        }
+        if (!bpFound) {
+            emuThread->gb.set_breakpoint(addr);
+            std::cout << "bp set at 0x" << std::hex << addr << std::endl;
+        }
+        disassembly->refresh();
+        status->update();
+    }
 }
 
 void QtBoiMainWindow::onRedraw(const uchar *buffer)
index 53f40c5b11ac8fec6d43883cf186673a5fc47304..5d6ef5c5118b60711896bde1b90d9568424e804c 100644 (file)
@@ -5,11 +5,11 @@
 #include "QtBoiStatusWindow.h"
 
 QtBoiStatusWindow::QtBoiStatusWindow(QWidget *parent, GameBoy *gb)
-        :gb(gb)
+:gb(gb)
 {
-        setFocusPolicy(Qt::NoFocus);
-        setMinimumSize(200,200);
-       setMaximumWidth(320);
+    setFocusPolicy(Qt::NoFocus);
+    setMinimumSize(200,200);
+    setMaximumWidth(320);
 }
 
 QtBoiStatusWindow::~QtBoiStatusWindow()
@@ -18,64 +18,74 @@ QtBoiStatusWindow::~QtBoiStatusWindow()
 
 void QtBoiStatusWindow::update()
 {
-        std::ostringstream str;
-        
-        str << "<html><head><title>Status</title></head><body>";
-        
-        str << "<table cellpadding=0 cellspacing=0><tr><td>";
-        // 8-bit regs table
-        str << "<table cellpadding=0 cellspacing=0>";
-        str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">8-bit</th></tr>";
-        str << "<tr><td>A&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.A) << "</td></tr>";
-        str << "<tr><td>B&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.B) << "</td></tr>";
-        str << "<tr><td>C&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.C) << "</td></tr>";
-        str << "<tr><td>D&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.D) << "</td></tr>";
-        str << "<tr><td>E&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.E) << "</td></tr>";
-        str << "<tr><td>H&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.H) << "</td></tr>";
-        str << "<tr><td>L&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.L) << "</td></tr>";
-        str << "</table>";
-        str << "</td><td width=20></td><td>";
-        // 16-bit regs table
-        str << "<table cellpadding=0 cellspacing=0>";
-        str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">16-bit</th></tr>";
-        str << "<tr><td>BC&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.BC) << "</td></tr>";
-        str << "<tr><td>DE&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.DE) << "</td></tr>";
-        str << "<tr><td>HL&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.HL) << "</td></tr>";
-        str << "<tr></tr>";
-        str << "<tr><td>SP&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.SP) << "</td></tr>";
-        str << "<tr></tr>";
-        str << "<tr><td>PC&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.PC) << "</td></tr>";
-        str << "</table>";
-        str << "</td><td width=20></td><td>";
-        // Flags & state table (TODO: Interpret IE and IF registers)
-        str << "<table cellpadding=0 cellspacing=0>";
-        str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">Flags</th></tr>";
-        str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::ZERO_FLAG)? "#FF0000" : "#FFFFFF") << ">Z</td></tr>";
-        str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::ADD_SUB_FLAG)? "#FF0000" : "#FFFFFF") << ">N</td></tr>";
-        str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::HALF_CARRY_FLAG)? "#FF0000" : "#FFFFFF") << ">H</td></tr>";
-        str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::CARRY_FLAG)? "#FF0000" : "#FFFFFF") << ">C</td></tr>";
-       str << "<tr><td>IME&nbsp;=&nbsp;" << int(gb->IME) << "</td></tr>";
-       int IE = int(gb->memory.read(0xFFFF, GBMemory::DONT_WATCH));
-       str << "<tr><td>IE&nbsp;&nbsp;=";
-       if (IE & GameBoy::IRQ_VBLANK)   str << "&nbsp;VBL";
-       if (IE & GameBoy::IRQ_LCD_STAT) str << "&nbsp;LCD";
-       if (IE & GameBoy::IRQ_TIMER)    str << "&nbsp;TIM";
-       if (IE & GameBoy::IRQ_SERIAL)   str << "&nbsp;SER";
-       if (IE & GameBoy::IRQ_JOYPAD)   str << "&nbsp;JOYP";
-       str << "</td></tr>";
-       int IF = int(gb->memory.read(0xFF0F, GBMemory::DONT_WATCH));
-       str << "<tr><td>IF&nbsp;&nbsp;=";
-       if (IF & GameBoy::IRQ_VBLANK)   str << "&nbsp;VBL";
-       if (IF & GameBoy::IRQ_LCD_STAT) str << "&nbsp;LCD";
-       if (IF & GameBoy::IRQ_TIMER)    str << "&nbsp;TIM";
-       if (IF & GameBoy::IRQ_SERIAL)   str << "&nbsp;SER";
-       if (IF & GameBoy::IRQ_JOYPAD)   str << "&nbsp;JOYP";
-       str << "</td></tr>";
-       str << "</table>";
-        str << "</td></tr>";
-        str << "</table></body></html>";
+    std::ostringstream str;
 
-        setHtml(QString(str.str().c_str()));
+    str << "<html><head><title>Status</title></head><body>";
+
+    str << "<table cellpadding=0 cellspacing=0><tr><td>";
+    // 8-bit regs table
+    str << "<table cellpadding=0 cellspacing=0>";
+    str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">8-bit</th></tr>";
+    str << "<tr><td>A&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.A) << "</td></tr>";
+    str << "<tr><td>B&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.B) << "</td></tr>";
+    str << "<tr><td>C&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.C) << "</td></tr>";
+    str << "<tr><td>D&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.D) << "</td></tr>";
+    str << "<tr><td>E&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.E) << "</td></tr>";
+    str << "<tr><td>H&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.H) << "</td></tr>";
+    str << "<tr><td>L&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(2) << std::setfill('0') << int(gb->regs.L) << "</td></tr>";
+    str << "</table>";
+    str << "</td><td width=20></td><td>";
+    // 16-bit regs table
+    str << "<table cellpadding=0 cellspacing=0>";
+    str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">16-bit</th></tr>";
+    str << "<tr><td>BC&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.BC) << "</td></tr>";
+    str << "<tr><td>DE&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.DE) << "</td></tr>";
+    str << "<tr><td>HL&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.HL) << "</td></tr>";
+    str << "<tr></tr>";
+    str << "<tr><td>SP&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.SP) << "</td></tr>";
+    str << "<tr></tr>";
+    str << "<tr><td>PC&nbsp;=&nbsp;</td><td>0x" << std::hex << std::setw(4) << std::setfill('0') << int(gb->regs.PC) << "</td></tr>";
+    str << "</table>";
+    str << "</td><td width=20></td><td>";
+    // Flags & state table (TODO: Interpret IE and IF registers)
+    str << "<table cellpadding=0 cellspacing=0>";
+    str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">Flags</th></tr>";
+    str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::ZERO_FLAG)? "#FF0000" : "#FFFFFF") << ">Z</td></tr>";
+    str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::ADD_SUB_FLAG)? "#FF0000" : "#FFFFFF") << ">N</td></tr>";
+    str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::HALF_CARRY_FLAG)? "#FF0000" : "#FFFFFF") << ">H</td></tr>";
+    str << "<tr><td bgcolor=" << (gb->check_flag(GameBoy::CARRY_FLAG)? "#FF0000" : "#FFFFFF") << ">C</td></tr>";
+    str << "<tr><td>IME&nbsp;=&nbsp;" << int(gb->IME) << "</td></tr>";
+    int IE = int(gb->memory.read(0xFFFF, GBMemory::DONT_WATCH));
+    str << "<tr><td>IE&nbsp;&nbsp;=";
+    if (IE & GameBoy::IRQ_VBLANK)   str << "&nbsp;VBL";
+    if (IE & GameBoy::IRQ_LCD_STAT) str << "&nbsp;LCD";
+    if (IE & GameBoy::IRQ_TIMER)    str << "&nbsp;TIM";
+    if (IE & GameBoy::IRQ_SERIAL)   str << "&nbsp;SER";
+    if (IE & GameBoy::IRQ_JOYPAD)   str << "&nbsp;JOYP";
+    str << "</td></tr>";
+    int IF = int(gb->memory.read(0xFF0F, GBMemory::DONT_WATCH));
+    str << "<tr><td>IF&nbsp;&nbsp;=";
+    if (IF & GameBoy::IRQ_VBLANK)   str << "&nbsp;VBL";
+    if (IF & GameBoy::IRQ_LCD_STAT) str << "&nbsp;LCD";
+    if (IF & GameBoy::IRQ_TIMER)    str << "&nbsp;TIM";
+    if (IF & GameBoy::IRQ_SERIAL)   str << "&nbsp;SER";
+    if (IF & GameBoy::IRQ_JOYPAD)   str << "&nbsp;JOYP";
+    str << "</td></tr>";
+    str << "</table>";
+    str << "</td></tr>";
+    str << "</table><hr>";
+
+    str << "<table>";
+    str << "<tr bgcolor=#c0ffc0><th colspan=\"2\">Breakpoints</th></tr>";
+    GameBoy::BreakpointMap bpmap = gb->get_breakpoints();
+    for (GameBoy::BreakpointMap::iterator i = bpmap.begin(); i != bpmap.end(); i++){
+        str << "<tr><td>&nbsp;" << i->first << "&nbsp;</td>";
+        str << "<td>0x" << std::hex << i->second.addr << (i->second.enabled? "":"(disabled)") << "</td></tr>";
+    }
+
+    str <<"</table></body></html>";
+
+    setHtml(QString(str.str().c_str()));
 }