return(os.str());
}
+template <class T>
+std::string toStringHex(const T &object, int width)
+{
+ std::ostringstream os;
+ os << "0x" << std::hex << std::setw(width) << std::setfill('0') << object;
+ return(os.str());
+}
+
#endif
#include <iomanip>
#include "QtBoiDisassemblyWindow.h"
+#include "../common/toString.h"
-QtBoiDisassemblyWindow::QtBoiDisassemblyWindow(QWidget *parent, GameBoy *gb)
- :gb(gb)
+QtBoiDisassemblyWindow::QtBoiDisassemblyWindow(QWidget *parent, GameBoy *gb, QHash<u32,QString> *tags)
+ :gb(gb), tags(tags)
{
setFocusPolicy(Qt::NoFocus);
- setMinimumSize(350,500);
+ setMinimumSize(500,500);
}
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 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 QtBoiDisassemblyWindow::insToHtml(const Instruction &ins)
+{
+
+ std::string 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.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;
+}
+
void QtBoiDisassemblyWindow::gotoAddress(u16 addr)
{
int start = addr;
str << "<html><head><title>Disassembly</title></head><body>";
str << "<table cellpadding=0 cellspacing=0>";
- str << "<tr bgcolor=#c0ffc0><td width=25%>Addr</td><td width=25%>Opcodes</td><td>Instruction</td></tr>";
+ str << "<tr bgcolor=#c0ffc0><td width=25%>Labels</td><td width=15%>Addr</td><td width=15%>Opcodes</td><td>Instruction</td></tr>";
bool hilightBG=true;
while (pos < end)
{
Instruction ins(gb->disassemble_opcode(pos));
- str << "<tr bgcolor=" << (pos == gb->regs.PC ? "#ffc0c0" : (hilightBG ? "#d0d0d0" : "#ffffff")) <<
- "><td>0x" << std::hex << std::setw(4) << std::setfill('0') <<
- pos << "</td><td>";
+ 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 << "</td><td>";
- str << ins.all << "</td></tr>";
+ str << insToHtml(ins) << "</td></tr>";
pos += ins.length;
hilightBG = !hilightBG;
str << "</table></body></html>";
setHtml(QString(str.str().c_str()));
+ currentAddress=addr;
}
void QtBoiDisassemblyWindow::gotoPC()
gotoAddress(gb->regs.PC);
}
+void QtBoiDisassemblyWindow::refresh()
+{
+ gotoAddress(currentAddress);
+}
+
+
+
#define QTBOIDISASSEMBLYWINDOW_H
#include <QTextBrowser>
+#include <QString>
+#include <QHash>
#include "../core/GameBoy.h"
class QtBoiDisassemblyWindow: public QTextBrowser
Q_OBJECT
public:
- QtBoiDisassemblyWindow(QWidget *parent, GameBoy *gb);
+ QtBoiDisassemblyWindow(QWidget *parent, GameBoy *gb, QHash<u32, QString> *tags);
~QtBoiDisassemblyWindow();
void gotoAddress(u16 addr);
void gotoPC();
+ void refresh();
private:
+ std::string insToHtml(const Instruction &ins);
+ std::string operandToHtml(const Instruction::Operand &ins);
+ std::string htmlLinkMem(u32 addr);
GameBoy *gb;
+ QString romTitle;
+ QHash<u32, QString> *tags;
+
+ u16 currentAddress;
};
void QtBoiEmuThread::loadROM(QString name)
{
+ pause();
romName=name;
- reset();
+ gb.load_ROM(romName.toStdString());
romLoaded=true;
}
if(resetRequested)
{
resetRequested = false;
- gb.load_ROM(romName.toStdString());
+ gb.reset();
if (isPaused) emit emulationPaused();
}
#include <QToolBar>
#include <QString>
#include <QFileDialog>
+#include <QInputDialog>
#include <QPushButton>
#include <QColor>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
+#include <QFile>
+#include <QTextStream>
+
#include <iostream>
#include "QtBoiMainWindow.h"
+#include "../core/GameBoy.h"
+#include "../core/GBRom.h"
QtBoiMainWindow::QtBoiMainWindow(QWidget *parent)
:QMainWindow(parent), emuThread(0)
loadROM->setShortcut(QKeySequence(tr("Ctrl+O", "File|Load ROM...")));
emulatorCont->setShortcut(QKeySequence(tr("F5", "Emulator|Go")));
+ emulatorPause->setShortcut(QKeySequence(tr("F6", "Emulator|Pause")));
emulatorStep->setShortcut(QKeySequence(tr("F7", "Debug|Step")));
//emulatorCont->setIcon(QIcon("../icons/player_play.svg"));
//emulatorPause->setIcon(QIcon("../icons/player_pause.svg"));
leftVBoxLayout->addWidget(screen);
leftVBoxLayout->addWidget(status);
- disassembly = new QtBoiDisassemblyWindow(centralWindow, &emuThread->gb);
+ disassembly = new QtBoiDisassemblyWindow(centralWindow, &emuThread->gb, &tags);
disassembly->setOpenLinks(false);
disassembly->setFont(QFont("courier"));
+
+ connect(disassembly, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(onDisassemblyAnchorClicked(const QUrl&)));
rightVBoxLayout->addWidget(disassembly);
QtBoiMainWindow::~QtBoiMainWindow()
{
+ if (romTitle != "")
+ saveTags();
+
if (emuThread) {
emuThread->stop();
emuThread->wait();
void QtBoiMainWindow::onLoadROM()
{
+ saveTags();
+
QString filename = QFileDialog::getOpenFileName(this, tr("Load ROM"), "../roms", tr("GameBoy ROMs (*.gb *.gbc)"));
if (filename == "") return;
emuThread->loadROM(filename);
- statusbar->showMessage(tr("Loaded ROM ")+filename);
+
+ char title[12];
+ memcpy(title, emuThread->gb.rom->header.new_title, 11);
+ title[11]='\0';
+ romTitle=QString(title);
+ loadTags();
+
+ statusbar->showMessage(tr("Loaded ROM ")+filename+" ["+romTitle+"]");
+}
+
+void QtBoiMainWindow::onDisassemblyAnchorClicked(const QUrl& url)
+{
+ std::cout << url.toString().toStdString() << std::endl;
+ if (url.scheme() == "gotoaddr") {
+ u32 addr = url.path().toUInt();
+ disassembly->gotoAddress(addr);
+ }
+ else if (url.scheme() == "newtag") {
+ u32 addr = url.path().toUInt();
+ QString tag = QInputDialog::getText(this, tr("Create new tag"), tr("Enter the tag for the selected address"),
+ QLineEdit::Normal, tags[addr]);
+
+ tags[addr] = tag;
+ disassembly->refresh();
+ }
}
void QtBoiMainWindow::onRedraw(const uchar *buffer)
+void QtBoiMainWindow::loadTags()
+{
+ this->romTitle = romTitle;
+ tags.clear();
+ QFile tagsfile(romTitle+".tags");
+ if (!tagsfile.open(QIODevice::ReadOnly | QIODevice::Text))
+ return;
+
+ QTextStream in(&tagsfile);
+ while(!in.atEnd())
+ {
+ u32 addr;
+ QString tag;
+
+ in >> addr;
+ in.skipWhiteSpace();
+ tag = in.readLine();
+
+ tags[addr] = tag;
+ }
+}
+
+void QtBoiMainWindow::saveTags()
+{
+ QFile tagsfile(romTitle+".tags");
+ if (!tagsfile.open(QIODevice::WriteOnly | QIODevice::Text))
+ return;
+
+ QTextStream out(&tagsfile);
+ QHashIterator<u32,QString> i(tags);
+ while (i.hasNext())
+ {
+ i.next();
+ out << i.key() << " " << i.value() << "\n";
+ }
+}
+
+
#include <QLabel>
#include <QStatusBar>
#include <QTextBrowser>
+#include <QString>
+#include <QUrl>
+#include <QHash>
#include "QtBoiEmuThread.h"
#include "QtBoiDisassemblyWindow.h"
public slots:
void onLoadROM();
+ void onDisassemblyAnchorClicked(const QUrl&);
void onRedraw(const uchar *buffer);
void onPause();
void onResume();
void createMenu();
void createToolbar();
+ void loadTags();
+ void saveTags();
+
// events
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
QtBoiEmuThread *emuThread;
+
+ QString romTitle;
+ QHash<u32, QString> tags;
QWidget *centralWindow;
QLabel *screen;
{
setFocusPolicy(Qt::NoFocus);
setMinimumSize(200,200);
+ setMaximumWidth(320);
}
QtBoiStatusWindow::~QtBoiStatusWindow()
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 = " << int(gb->IME) << "</td></tr>";
- str << "<tr><td>IE = " << int(gb->memory.read(0xFFFF, GBMemory::DONT_WATCH)) << "</td></tr>";
- str << "<tr><td>IF = " << int(gb->memory.read(0xFF0F, GBMemory::DONT_WATCH)) << "</td></tr>";
+ int IE = int(gb->memory.read(0xFFFF, GBMemory::DONT_WATCH));
+ str << "<tr><td>IE =";
+ if (IE & GameBoy::IRQ_VBLANK) str << " VBL";
+ if (IE & GameBoy::IRQ_LCD_STAT) str << " LCD";
+ if (IE & GameBoy::IRQ_TIMER) str << " TIM";
+ if (IE & GameBoy::IRQ_SERIAL) str << " SER";
+ if (IE & GameBoy::IRQ_JOYPAD) str << " JOYP";
+ str << "</td></tr>";
+ int IF = int(gb->memory.read(0xFF0F, GBMemory::DONT_WATCH));
+ str << "<tr><td>IF =";
+ if (IF & GameBoy::IRQ_VBLANK) str << " VBL";
+ if (IF & GameBoy::IRQ_LCD_STAT) str << " LCD";
+ if (IF & GameBoy::IRQ_TIMER) str << " TIM";
+ if (IF & GameBoy::IRQ_SERIAL) str << " SER";
+ if (IF & GameBoy::IRQ_JOYPAD) str << " JOYP";
+ str << "</td></tr>";
+ str << "</table>";
str << "</td></tr>";
str << "</table></body></html>";