Qt: Add debugger UI

This commit is contained in:
Connor McLaughlin 2020-12-17 01:18:13 +10:00
parent 3b23542ec9
commit ea996a0305
28 changed files with 2093 additions and 41 deletions

View file

@ -17,6 +17,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
## Latest News
- 2020/12/16: Integrated CPU debugger added in Qt frontend.
- 2020/12/13: Button layout for the touchscreen controller in the Android version can now be customized.
- 2020/12/10: Translation support added for Android version. Currently Brazillian Portuguese, Italian, and Dutch are available.
- 2020/11/27: Cover support added for game list in Android version. Procedure is the same as the desktop version, except you should place cover images in `<storage>/duckstation/covers` (see [Adding Game Covers](https://github.com/stenzek/duckstation/wiki/Adding-Game-Covers)).

View file

@ -29,6 +29,11 @@ set(SRCS
consolesettingswidget.ui
controllersettingswidget.cpp
controllersettingswidget.h
debuggermodels.cpp
debuggermodels.h
debuggerwindow.cpp
debuggerwindow.h
debuggerwindow.ui
displaysettingswidget.cpp
displaysettingswidget.h
displaysettingswidget.ui
@ -68,6 +73,8 @@ set(SRCS
memorycardeditordialog.ui
memorycardsettingswidget.cpp
memorycardsettingswidget.h
memoryviewwidget.cpp
memoryviewwidget.h
postprocessingchainconfigwidget.cpp
postprocessingchainconfigwidget.h
postprocessingchainconfigwidget.ui

View file

@ -0,0 +1,417 @@
#include "debuggermodels.h"
#include "common/log.h"
#include "core/cpu_core.h"
#include "core/cpu_core_private.h"
#include "core/cpu_disasm.h"
#include <QtGui/QColor>
#include <QtGui/QIcon>
#include <QtGui/QPalette>
#include <QtWidgets/QApplication>
Log_SetChannel(DebuggerModels);
static constexpr int NUM_COLUMNS = 5;
static constexpr int STACK_RANGE = 128;
static constexpr u32 STACK_VALUE_SIZE = sizeof(u32);
DebuggerCodeModel::DebuggerCodeModel(QObject* parent /*= nullptr*/) : QAbstractTableModel(parent)
{
resetCodeView(0);
m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(QSize(12, 12));
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(QSize(12, 12));
}
DebuggerCodeModel::~DebuggerCodeModel() {}
int DebuggerCodeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE);
}
int DebuggerCodeModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return NUM_COLUMNS;
}
int DebuggerCodeModel::getRowForAddress(VirtualMemoryAddress address) const
{
return static_cast<int>((address - m_code_region_start) / CPU::INSTRUCTION_SIZE);
}
VirtualMemoryAddress DebuggerCodeModel::getAddressForRow(int row) const
{
return m_code_region_start + (static_cast<u32>(row) * CPU::INSTRUCTION_SIZE);
}
VirtualMemoryAddress DebuggerCodeModel::getAddressForIndex(QModelIndex index) const
{
return getAddressForRow(index.row());
}
int DebuggerCodeModel::getRowForPC() const
{
return getRowForAddress(m_last_pc);
}
QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
if (index.column() < 0 || index.column() >= NUM_COLUMNS)
return QVariant();
if (role == Qt::DisplayRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
switch (index.column())
{
case 0:
// breakpoint
return QVariant();
case 1:
{
// Address
return QVariant(QString::asprintf("0x%08X", address));
}
case 2:
{
// Bytes
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return QStringLiteral("<invalid>");
return QString::asprintf("%08X", instruction_bits);
}
case 3:
{
// Instruction
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return QStringLiteral("<invalid>");
Log_DevPrintf("Disassemble %08X", address);
SmallString str;
CPU::DisassembleInstruction(&str, address, instruction_bits);
return QString::fromUtf8(str.GetCharArray(), static_cast<int>(str.GetLength()));
}
case 4:
{
// Comment
if (address != m_last_pc)
return QVariant();
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return QStringLiteral("<invalid>");
TinyString str;
CPU::DisassembleInstructionComment(&str, address, instruction_bits, &CPU::g_state.regs);
return QString::fromUtf8(str.GetCharArray(), static_cast<int>(str.GetLength()));
}
default:
return QVariant();
}
}
else if (role == Qt::DecorationRole)
{
if (index.column() == 0)
{
// breakpoint
const VirtualMemoryAddress address = getAddressForRow(index.row());
if (m_last_pc == address)
return m_pc_pixmap;
else if (hasBreakpointAtAddress(address))
return m_breakpoint_pixmap;
}
return QVariant();
}
else if (role == Qt::BackgroundRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
// breakpoint
if (hasBreakpointAtAddress(address))
return QVariant(QColor(171, 97, 107));
// if (address == m_last_pc)
// return QApplication::palette().toolTipBase();
if (address == m_last_pc)
return QColor(100, 100, 0);
else
return QVariant();
}
else if (role == Qt::ForegroundRole)
{
const VirtualMemoryAddress address = getAddressForRow(index.row());
// if (address == m_last_pc)
// return QApplication::palette().toolTipText();
if (address == m_last_pc || hasBreakpointAtAddress(address))
return QColor(Qt::white);
else
return QVariant();
}
else
{
return QVariant();
}
}
QVariant DebuggerCodeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
static const char* header_names[] = {"", "Address", "Bytes", "Instruction", "Comment"};
if (section < 0 || section >= countof(header_names))
return QVariant();
return header_names[section];
}
bool DebuggerCodeModel::updateRegion(VirtualMemoryAddress address)
{
CPU::Segment segment = CPU::GetSegmentForAddress(address);
std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address));
if (!region.has_value() || (address >= m_code_region_start && address < m_code_region_end))
return false;
static constexpr unsigned NUM_INSTRUCTIONS_BEFORE = 4096;
static constexpr unsigned NUM_INSTRUCTIONS_AFTER = 4096;
static constexpr unsigned NUM_BYTES_BEFORE = NUM_INSTRUCTIONS_BEFORE * sizeof(CPU::Instruction);
static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(CPU::Instruction);
const VirtualMemoryAddress start_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment);
const VirtualMemoryAddress end_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment);
beginResetModel();
m_code_region_start = ((address - start_address) < NUM_BYTES_BEFORE) ? start_address : (address - NUM_BYTES_BEFORE);
m_code_region_end = ((end_address - address) < NUM_BYTES_AFTER) ? end_address : (address + NUM_BYTES_AFTER);
m_current_segment = segment;
m_current_code_region = region.value();
endResetModel();
return true;
}
bool DebuggerCodeModel::emitDataChangedForAddress(VirtualMemoryAddress address)
{
CPU::Segment segment = CPU::GetSegmentForAddress(address);
std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address));
if (!region.has_value() || segment != m_current_segment || region != m_current_code_region)
return false;
const int row = getRowForAddress(address);
emit dataChanged(index(row, 0), index(row, NUM_COLUMNS - 1));
return true;
}
bool DebuggerCodeModel::hasBreakpointAtAddress(VirtualMemoryAddress address) const
{
return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end();
}
void DebuggerCodeModel::resetCodeView(VirtualMemoryAddress start_address)
{
updateRegion(start_address);
}
void DebuggerCodeModel::setPC(VirtualMemoryAddress pc)
{
const VirtualMemoryAddress prev_pc = m_last_pc;
m_last_pc = pc;
if (!updateRegion(pc))
{
emitDataChangedForAddress(prev_pc);
emitDataChangedForAddress(pc);
}
}
void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address)
{
updateRegion(address);
}
void DebuggerCodeModel::setBreakpointList(std::vector<VirtualMemoryAddress> bps)
{
clearBreakpoints();
m_breakpoints = std::move(bps);
for (VirtualMemoryAddress bp : m_breakpoints)
emitDataChangedForAddress(bp);
}
void DebuggerCodeModel::clearBreakpoints()
{
std::vector<VirtualMemoryAddress> old_bps(std::move(m_breakpoints));
for (VirtualMemoryAddress old_bp : old_bps)
emitDataChangedForAddress(old_bp);
}
void DebuggerCodeModel::setBreakpointState(VirtualMemoryAddress address, bool enabled)
{
if (enabled)
{
if (std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end())
return;
m_breakpoints.push_back(address);
emitDataChangedForAddress(address);
}
else
{
auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address);
if (it == m_breakpoints.end())
return;
m_breakpoints.erase(it);
emitDataChangedForAddress(address);
}
}
DebuggerRegistersModel::DebuggerRegistersModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) {}
DebuggerRegistersModel::~DebuggerRegistersModel() {}
int DebuggerRegistersModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return static_cast<int>(CPU::Reg::count);
}
int DebuggerRegistersModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return 2;
}
QVariant DebuggerRegistersModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
u32 reg_index = static_cast<u32>(index.row());
if (reg_index >= static_cast<u32>(CPU::Reg::count))
return QVariant();
if (index.column() < 0 || index.column() > 1)
return QVariant();
switch (index.column())
{
case 0: // address
{
if (role == Qt::DisplayRole)
return QString::fromUtf8(CPU::GetRegName(static_cast<CPU::Reg>(reg_index)));
}
break;
case 1: // data
{
if (role == Qt::DisplayRole)
{
return QString::asprintf("0x%08X", CPU::g_state.regs.r[reg_index]);
}
else if (role == Qt::ForegroundRole)
{
if (CPU::g_state.regs.r[reg_index] != m_old_reg_values[reg_index])
return QColor(255, 50, 50);
}
}
break;
default:
break;
}
return QVariant();
}
QVariant DebuggerRegistersModel::headerData(int section, Qt::Orientation orientation,
int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
static const char* header_names[] = {"Register", "Value"};
if (section < 0 || section >= countof(header_names))
return QVariant();
return header_names[section];
}
void DebuggerRegistersModel::invalidateView()
{
beginResetModel();
endResetModel();
}
void DebuggerRegistersModel::saveCurrentValues()
{
for (u32 i = 0; i < static_cast<u32>(CPU::Reg::count); i++)
m_old_reg_values[i] = CPU::g_state.regs.r[i];
}
DebuggerStackModel::DebuggerStackModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) {}
DebuggerStackModel::~DebuggerStackModel() {}
int DebuggerStackModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return STACK_RANGE * 2;
}
int DebuggerStackModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const
{
return 2;
}
QVariant DebuggerStackModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
if (index.column() < 0 || index.column() > 1)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
const u32 sp = CPU::g_state.regs.sp;
const VirtualMemoryAddress address =
(sp - static_cast<u32>(STACK_RANGE * STACK_VALUE_SIZE)) + static_cast<u32>(index.row()) * STACK_VALUE_SIZE;
if (index.column() == 0)
return QString::asprintf("0x%08X", address);
u32 value;
if (!CPU::SafeReadMemoryWord(address, &value))
return QStringLiteral("<invalid>");
return QString::asprintf("0x%08X", ZeroExtend32(value));
}
QVariant DebuggerStackModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
if (orientation != Qt::Horizontal)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
static const char* header_names[] = {"Address", "Value"};
if (section < 0 || section >= countof(header_names))
return QVariant();
return header_names[section];
}
void DebuggerStackModel::invalidateView()
{
beginResetModel();
endResetModel();
}

View file

@ -0,0 +1,84 @@
#pragma once
#include "core/bus.h"
#include "core/cpu_types.h"
#include <QtCore/QAbstractListModel>
#include <QtCore/QAbstractTableModel>
#include <QtGui/QPixmap>
#include <map>
class DebuggerCodeModel : public QAbstractTableModel
{
Q_OBJECT
public:
DebuggerCodeModel(QObject* parent = nullptr);
virtual ~DebuggerCodeModel();
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// Returns the row for this instruction pointer
void resetCodeView(VirtualMemoryAddress start_address);
int getRowForAddress(VirtualMemoryAddress address) const;
int getRowForPC() const;
VirtualMemoryAddress getAddressForRow(int row) const;
VirtualMemoryAddress getAddressForIndex(QModelIndex index) const;
void setPC(VirtualMemoryAddress pc);
void ensureAddressVisible(VirtualMemoryAddress address);
void setBreakpointList(std::vector<VirtualMemoryAddress> bps);
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
void clearBreakpoints();
private:
bool updateRegion(VirtualMemoryAddress address);
bool emitDataChangedForAddress(VirtualMemoryAddress address);
bool hasBreakpointAtAddress(VirtualMemoryAddress address) const;
Bus::MemoryRegion m_current_code_region = Bus::MemoryRegion::Count;
CPU::Segment m_current_segment = CPU::Segment::KUSEG;
VirtualMemoryAddress m_code_region_start = 0;
VirtualMemoryAddress m_code_region_end = 0;
VirtualMemoryAddress m_last_pc = 0;
std::vector<VirtualMemoryAddress> m_breakpoints;
QPixmap m_pc_pixmap;
QPixmap m_breakpoint_pixmap;
};
class DebuggerRegistersModel : public QAbstractListModel
{
Q_OBJECT
public:
DebuggerRegistersModel(QObject* parent = nullptr);
virtual ~DebuggerRegistersModel();
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void invalidateView();
void saveCurrentValues();
private:
u32 m_old_reg_values[static_cast<u32>(CPU::Reg::count)] = {};
};
class DebuggerStackModel : public QAbstractListModel
{
Q_OBJECT
public:
DebuggerStackModel(QObject* parent = nullptr);
virtual ~DebuggerStackModel();
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
void invalidateView();
};

View file

@ -0,0 +1,597 @@
#include "debuggerwindow.h"
#include "core/cpu_core_private.h"
#include "debuggermodels.h"
#include "qthostinterface.h"
#include <QtCore/QSignalBlocker>
#include <QtGui/QFontDatabase>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QMessageBox>
DebuggerWindow::DebuggerWindow(QWidget* parent /* = nullptr */)
: QMainWindow(parent), m_active_memory_region(Bus::MemoryRegion::Count)
{
m_ui.setupUi(this);
setupAdditionalUi();
connectSignals();
createModels();
setMemoryViewRegion(Bus::MemoryRegion::RAM);
setUIEnabled(false);
}
DebuggerWindow::~DebuggerWindow() {}
void DebuggerWindow::onEmulationPaused(bool paused)
{
if (paused)
{
setUIEnabled(true);
refreshAll();
refreshBreakpointList();
}
else
{
setUIEnabled(false);
}
{
QSignalBlocker sb(m_ui.actionPause);
m_ui.actionPause->setChecked(paused);
}
}
void DebuggerWindow::onDebuggerMessageReported(const QString& message)
{
m_ui.statusbar->showMessage(message, 0);
}
void DebuggerWindow::refreshAll()
{
m_registers_model->invalidateView();
m_stack_model->invalidateView();
m_ui.memoryView->repaint();
m_code_model->setPC(CPU::g_state.regs.pc);
scrollToPC();
}
void DebuggerWindow::scrollToPC()
{
return scrollToCodeAddress(CPU::g_state.regs.pc);
}
void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address)
{
m_code_model->ensureAddressVisible(address);
int row = m_code_model->getRowForAddress(address);
if (row >= 0)
{
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
m_ui.codeView->scrollTo(m_code_model->index(row, 0));
}
}
void DebuggerWindow::onPauseActionToggled(bool paused)
{
if (!paused)
{
m_registers_model->saveCurrentValues();
setUIEnabled(false);
}
QtHostInterface::GetInstance()->pauseSystem(paused);
}
void DebuggerWindow::onRunToCursorTriggered()
{
std::optional<VirtualMemoryAddress> addr = getSelectedCodeAddress();
if (!addr.has_value())
{
QMessageBox::critical(this, windowTitle(), tr("No address selected."));
return;
}
CPU::AddBreakpoint(addr.value(), true, true);
QtHostInterface::GetInstance()->pauseSystem(false);
}
void DebuggerWindow::onGoToPCTriggered()
{
scrollToPC();
}
void DebuggerWindow::onGoToAddressTriggered()
{
std::optional<VirtualMemoryAddress> address = promptForAddress(tr("Enter code address:"));
if (!address.has_value())
return;
scrollToCodeAddress(address.value());
}
void DebuggerWindow::onDumpAddressTriggered()
{
std::optional<VirtualMemoryAddress> address = promptForAddress(tr("Enter memory address:"));
if (!address.has_value())
return;
scrollToMemoryAddress(address.value());
}
void DebuggerWindow::onFollowAddressTriggered()
{
//
}
void DebuggerWindow::onAddBreakpointTriggered()
{
std::optional<VirtualMemoryAddress> address = promptForAddress(tr("Enter code address:"));
if (!address.has_value())
return;
if (CPU::HasBreakpointAtAddress(address.value()))
{
QMessageBox::critical(this, windowTitle(), tr("A breakpoint already exists at this address."));
return;
}
toggleBreakpoint(address.value());
}
void DebuggerWindow::onToggleBreakpointTriggered()
{
std::optional<VirtualMemoryAddress> address = getSelectedCodeAddress();
if (!address.has_value())
return;
toggleBreakpoint(address.value());
}
void DebuggerWindow::onClearBreakpointsTriggered()
{
clearBreakpoints();
}
void DebuggerWindow::onStepIntoActionTriggered()
{
Assert(System::IsPaused());
m_registers_model->saveCurrentValues();
QtHostInterface::GetInstance()->singleStepCPU();
refreshAll();
}
void DebuggerWindow::onStepOverActionTriggered()
{
Assert(System::IsPaused());
if (!CPU::AddStepOverBreakpoint())
{
onStepIntoActionTriggered();
return;
}
// unpause to let it run to the breakpoint
m_registers_model->saveCurrentValues();
QtHostInterface::GetInstance()->pauseSystem(false);
}
void DebuggerWindow::onStepOutActionTriggered()
{
Assert(System::IsPaused());
if (!CPU::AddStepOutBreakpoint())
{
QMessageBox::critical(this, tr("Debugger"), tr("Failed to add step-out breakpoint, are you in a valid function?"));
return;
}
// unpause to let it run to the breakpoint
m_registers_model->saveCurrentValues();
QtHostInterface::GetInstance()->pauseSystem(false);
}
void DebuggerWindow::onCodeViewItemActivated(QModelIndex index)
{
if (!index.isValid())
return;
const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index);
switch (index.column())
{
case 0: // breakpoint
case 3: // disassembly
toggleBreakpoint(address);
break;
case 1: // address
case 2: // bytes
scrollToMemoryAddress(address);
break;
case 4: // comment
tryFollowLoadStore(address);
break;
}
}
void DebuggerWindow::onMemorySearchTriggered()
{
m_ui.memoryView->clearHighlightRange();
const QString pattern_str = m_ui.memorySearchString->text();
if (pattern_str.isEmpty())
return;
std::vector<u8> pattern;
std::vector<u8> mask;
u8 spattern = 0;
u8 smask = 0;
bool msb = false;
pattern.reserve(static_cast<size_t>(pattern_str.length()) / 2);
mask.reserve(static_cast<size_t>(pattern_str.length()) / 2);
for (int i = 0; i < pattern_str.length(); i++)
{
const QChar ch = pattern_str[i];
if (ch == ' ')
continue;
if (ch == '?')
{
spattern = (spattern << 4);
smask = (smask << 4);
}
else if (ch.isDigit())
{
spattern = (spattern << 4) | static_cast<u8>(ch.digitValue());
smask = (smask << 4) | 0xF;
}
else if (ch.unicode() >= 'a' && ch.unicode() <= 'f')
{
spattern = (spattern << 4) | (0xA + static_cast<u8>(ch.unicode() - 'a'));
smask = (smask << 4) | 0xF;
}
else if (ch.unicode() >= 'A' && ch.unicode() <= 'F')
{
spattern = (spattern << 4) | (0xA + static_cast<u8>(ch.unicode() - 'A'));
smask = (smask << 4) | 0xF;
}
else
{
QMessageBox::critical(this, windowTitle(),
tr("Invalid search pattern. It should contain hex digits or question marks."));
return;
}
if (msb)
{
pattern.push_back(spattern);
mask.push_back(smask);
spattern = 0;
smask = 0;
}
msb = !msb;
}
if (msb)
{
// partial byte on the end
spattern = (spattern << 4);
smask = (smask << 4);
pattern.push_back(spattern);
mask.push_back(smask);
}
if (pattern.empty())
{
QMessageBox::critical(this, windowTitle(),
tr("Invalid search pattern. It should contain hex digits or question marks."));
return;
}
std::optional<PhysicalMemoryAddress> found_address =
Bus::SearchMemory(m_next_memory_search_address, pattern.data(), mask.data(), static_cast<u32>(pattern.size()));
bool wrapped_around = false;
if (!found_address.has_value())
{
found_address = Bus::SearchMemory(0, pattern.data(), mask.data(), static_cast<u32>(pattern.size()));
if (!found_address.has_value())
{
m_ui.statusbar->showMessage(tr("Pattern not found."));
return;
}
wrapped_around = true;
}
m_next_memory_search_address = found_address.value() + 1;
if (scrollToMemoryAddress(found_address.value()))
{
const size_t highlight_offset = found_address.value() - m_ui.memoryView->addressOffset();
m_ui.memoryView->setHighlightRange(highlight_offset, highlight_offset + pattern.size());
}
if (wrapped_around)
{
m_ui.statusbar->showMessage(tr("Pattern found at 0x%1 (passed the end of memory).")
.arg(static_cast<uint>(found_address.value()), 8, 10, static_cast<QChar>('0')));
}
else
{
m_ui.statusbar->showMessage(
tr("Pattern found at 0x%1.").arg(static_cast<uint>(found_address.value()), 8, 10, static_cast<QChar>('0')));
}
}
void DebuggerWindow::onMemorySearchStringChanged(const QString&)
{
m_next_memory_search_address = 0;
}
void DebuggerWindow::closeEvent(QCloseEvent* event)
{
QMainWindow::closeEvent(event);
QtHostInterface::GetInstance()->pauseSystem(true, true);
CPU::ClearBreakpoints();
QtHostInterface::GetInstance()->pauseSystem(false);
emit closed();
}
void DebuggerWindow::setupAdditionalUi()
{
#ifdef WIN32
QFont fixedFont;
fixedFont.setFamily(QStringLiteral("Consolas"));
fixedFont.setFixedPitch(true);
fixedFont.setStyleHint(QFont::TypeWriter);
fixedFont.setPointSize(10);
#else
const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
#endif
m_ui.codeView->setFont(fixedFont);
m_ui.registerView->setFont(fixedFont);
m_ui.memoryView->setFont(fixedFont);
m_ui.stackView->setFont(fixedFont);
setCentralWidget(nullptr);
delete m_ui.centralwidget;
}
void DebuggerWindow::connectSignals()
{
QtHostInterface* hi = QtHostInterface::GetInstance();
connect(hi, &QtHostInterface::emulationPaused, this, &DebuggerWindow::onEmulationPaused);
connect(hi, &QtHostInterface::debuggerMessageReported, this, &DebuggerWindow::onDebuggerMessageReported);
connect(m_ui.actionPause, &QAction::toggled, this, &DebuggerWindow::onPauseActionToggled);
connect(m_ui.actionRunToCursor, &QAction::triggered, this, &DebuggerWindow::onRunToCursorTriggered);
connect(m_ui.actionGoToPC, &QAction::triggered, this, &DebuggerWindow::onGoToPCTriggered);
connect(m_ui.actionGoToAddress, &QAction::triggered, this, &DebuggerWindow::onGoToAddressTriggered);
connect(m_ui.actionDumpAddress, &QAction::triggered, this, &DebuggerWindow::onDumpAddressTriggered);
connect(m_ui.actionStepInto, &QAction::triggered, this, &DebuggerWindow::onStepIntoActionTriggered);
connect(m_ui.actionStepOver, &QAction::triggered, this, &DebuggerWindow::onStepOverActionTriggered);
connect(m_ui.actionStepOut, &QAction::triggered, this, &DebuggerWindow::onStepOutActionTriggered);
connect(m_ui.actionAddBreakpoint, &QAction::triggered, this, &DebuggerWindow::onAddBreakpointTriggered);
connect(m_ui.actionToggleBreakpoint, &QAction::triggered, this, &DebuggerWindow::onToggleBreakpointTriggered);
connect(m_ui.actionClearBreakpoints, &QAction::triggered, this, &DebuggerWindow::onClearBreakpointsTriggered);
connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close);
connect(m_ui.codeView, &QTreeView::activated, this, &DebuggerWindow::onCodeViewItemActivated);
connect(m_ui.memoryRegionRAM, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::RAM); });
connect(m_ui.memoryRegionEXP1, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::EXP1); });
connect(m_ui.memoryRegionScratchpad, &QRadioButton::clicked,
[this]() { setMemoryViewRegion(Bus::MemoryRegion::Scratchpad); });
connect(m_ui.memoryRegionBIOS, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::BIOS); });
connect(m_ui.memorySearch, &QPushButton::clicked, this, &DebuggerWindow::onMemorySearchTriggered);
connect(m_ui.memorySearchString, &QLineEdit::textChanged, this, &DebuggerWindow::onMemorySearchStringChanged);
}
void DebuggerWindow::disconnectSignals()
{
QtHostInterface* hi = QtHostInterface::GetInstance();
hi->disconnect(this);
}
void DebuggerWindow::createModels()
{
m_code_model = std::make_unique<DebuggerCodeModel>();
m_ui.codeView->setModel(m_code_model.get());
// set default column width in code view
m_ui.codeView->setColumnWidth(0, 40);
m_ui.codeView->setColumnWidth(1, 80);
m_ui.codeView->setColumnWidth(2, 80);
m_ui.codeView->setColumnWidth(3, 250);
m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 80 + 80 + 250));
m_registers_model = std::make_unique<DebuggerRegistersModel>();
m_ui.registerView->setModel(m_registers_model.get());
// m_ui->registerView->resizeRowsToContents();
m_stack_model = std::make_unique<DebuggerStackModel>();
m_ui.stackView->setModel(m_stack_model.get());
m_ui.breakpointsWidget->setColumnWidth(0, 50);
m_ui.breakpointsWidget->setColumnWidth(1, 80);
m_ui.breakpointsWidget->setColumnWidth(2, 40);
m_ui.breakpointsWidget->setRootIsDecorated(false);
}
void DebuggerWindow::setUIEnabled(bool enabled)
{
// Disable all UI elements that depend on execution state
m_ui.codeView->setEnabled(enabled);
m_ui.registerView->setEnabled(enabled);
m_ui.stackView->setEnabled(enabled);
m_ui.memoryView->setEnabled(enabled);
m_ui.actionRunToCursor->setEnabled(enabled);
m_ui.actionAddBreakpoint->setEnabled(enabled);
m_ui.actionToggleBreakpoint->setEnabled(enabled);
m_ui.actionClearBreakpoints->setEnabled(enabled);
m_ui.actionDumpAddress->setEnabled(enabled);
m_ui.actionStepInto->setEnabled(enabled);
m_ui.actionStepOver->setEnabled(enabled);
m_ui.actionStepOut->setEnabled(enabled);
m_ui.actionGoToAddress->setEnabled(enabled);
m_ui.actionGoToPC->setEnabled(enabled);
m_ui.memoryRegionRAM->setEnabled(enabled);
m_ui.memoryRegionEXP1->setEnabled(enabled);
m_ui.memoryRegionScratchpad->setEnabled(enabled);
m_ui.memoryRegionBIOS->setEnabled(enabled);
}
void DebuggerWindow::setMemoryViewRegion(Bus::MemoryRegion region)
{
if (m_active_memory_region == region)
return;
m_active_memory_region = region;
switch (region)
{
case Bus::MemoryRegion::RAM:
case Bus::MemoryRegion::RAMMirror1:
case Bus::MemoryRegion::RAMMirror2:
case Bus::MemoryRegion::RAMMirror3:
m_ui.memoryView->setData(Bus::GetMemoryRegionStart(region), Bus::g_ram, Bus::RAM_SIZE);
break;
case Bus::MemoryRegion::Scratchpad:
m_ui.memoryView->setData(CPU::DCACHE_LOCATION, CPU::g_state.dcache.data(), CPU::DCACHE_SIZE);
break;
case Bus::MemoryRegion::BIOS:
m_ui.memoryView->setData(Bus::BIOS_BASE, Bus::g_bios, Bus::BIOS_SIZE);
break;
case Bus::MemoryRegion::EXP1:
default:
// TODO
m_ui.memoryView->setData(Bus::EXP1_BASE, nullptr, 0);
break;
}
#define SET_REGION_RADIO_BUTTON(name, rb_region) \
do \
{ \
QSignalBlocker sb(name); \
name->setChecked(region == rb_region); \
} while (0)
SET_REGION_RADIO_BUTTON(m_ui.memoryRegionRAM, Bus::MemoryRegion::RAM);
SET_REGION_RADIO_BUTTON(m_ui.memoryRegionEXP1, Bus::MemoryRegion::EXP1);
SET_REGION_RADIO_BUTTON(m_ui.memoryRegionScratchpad, Bus::MemoryRegion::Scratchpad);
SET_REGION_RADIO_BUTTON(m_ui.memoryRegionBIOS, Bus::MemoryRegion::BIOS);
#undef SET_REGION_REGION_BUTTON
m_ui.memoryView->repaint();
}
void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address)
{
const bool new_bp_state = !CPU::HasBreakpointAtAddress(address);
if (new_bp_state)
{
if (!CPU::AddBreakpoint(address, false))
return;
}
else
{
if (!CPU::RemoveBreakpoint(address))
return;
}
m_code_model->setBreakpointState(address, new_bp_state);
refreshBreakpointList();
}
void DebuggerWindow::clearBreakpoints()
{
m_code_model->clearBreakpoints();
CPU::ClearBreakpoints();
}
std::optional<VirtualMemoryAddress> DebuggerWindow::promptForAddress(const QString& label)
{
const QString address_str(QInputDialog::getText(this, windowTitle(), tr("Enter memory address:")));
if (address_str.isEmpty())
return std::nullopt;
bool ok;
uint address;
if (address_str.startsWith("0x"))
address = address_str.midRef(2).toUInt(&ok, 16);
else if (address_str[0] == '0')
address = address_str.midRef(2).toUInt(&ok, 8);
else
address = address_str.midRef(2).toUInt(&ok, 8);
if (!ok)
{
QMessageBox::critical(this, windowTitle(),
tr("Invalid address. It should be in hex (0x12345678) or decimal (12345678)"));
return std::nullopt;
}
return address;
}
std::optional<VirtualMemoryAddress> DebuggerWindow::getSelectedCodeAddress()
{
QItemSelectionModel* sel_model = m_ui.codeView->selectionModel();
const QModelIndexList indices(sel_model->selectedIndexes());
if (indices.empty())
return std::nullopt;
return m_code_model->getAddressForIndex(indices[0]);
}
bool DebuggerWindow::tryFollowLoadStore(VirtualMemoryAddress address)
{
CPU::Instruction inst;
if (!CPU::SafeReadInstruction(address, &inst.bits))
return false;
const std::optional<VirtualMemoryAddress> ea = GetLoadStoreEffectiveAddress(inst, &CPU::g_state.regs);
if (!ea.has_value())
return false;
scrollToMemoryAddress(ea.value());
return true;
}
bool DebuggerWindow::scrollToMemoryAddress(VirtualMemoryAddress address)
{
const PhysicalMemoryAddress phys_address = CPU::VirtualAddressToPhysical(address);
std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(phys_address);
if (!region.has_value())
return false;
setMemoryViewRegion(region.value());
const PhysicalMemoryAddress offset = phys_address - Bus::GetMemoryRegionStart(region.value());
m_ui.memoryView->scrolltoOffset(offset);
return true;
}
void DebuggerWindow::refreshBreakpointList()
{
while (m_ui.breakpointsWidget->topLevelItemCount() > 0)
delete m_ui.breakpointsWidget->takeTopLevelItem(0);
const CPU::BreakpointList bps(CPU::GetBreakpointList());
for (const CPU::Breakpoint& bp : bps)
{
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
item->setCheckState(0, bp.enabled ? Qt::Checked : Qt::Unchecked);
item->setText(0, QString::asprintf("%u", bp.number));
item->setText(1, QString::asprintf("0x%08X", bp.address));
item->setText(2, QString::asprintf("%u", bp.hit_count));
m_ui.breakpointsWidget->addTopLevelItem(item);
}
}

View file

@ -0,0 +1,79 @@
#pragma once
#include "core/types.h"
#include "ui_debuggerwindow.h"
#include <QtWidgets/QMainWindow>
#include <memory>
#include <optional>
namespace Bus {
enum class MemoryRegion;
}
class DebuggerCodeModel;
class DebuggerRegistersModel;
class DebuggerStackModel;
class DebuggerWindow : public QMainWindow
{
Q_OBJECT
public:
explicit DebuggerWindow(QWidget* parent = nullptr);
~DebuggerWindow();
Q_SIGNALS:
void closed();
protected:
void closeEvent(QCloseEvent* event);
private Q_SLOTS:
void onEmulationPaused(bool paused);
void onDebuggerMessageReported(const QString& message);
void refreshAll();
void scrollToPC();
void onPauseActionToggled(bool paused);
void onRunToCursorTriggered();
void onGoToPCTriggered();
void onGoToAddressTriggered();
void onDumpAddressTriggered();
void onFollowAddressTriggered();
void onAddBreakpointTriggered();
void onToggleBreakpointTriggered();
void onClearBreakpointsTriggered();
void onStepIntoActionTriggered();
void onStepOverActionTriggered();
void onStepOutActionTriggered();
void onCodeViewItemActivated(QModelIndex index);
void onMemorySearchTriggered();
void onMemorySearchStringChanged(const QString&);
private:
void setupAdditionalUi();
void connectSignals();
void disconnectSignals();
void createModels();
void setUIEnabled(bool enabled);
void setMemoryViewRegion(Bus::MemoryRegion region);
void toggleBreakpoint(VirtualMemoryAddress address);
void clearBreakpoints();
std::optional<VirtualMemoryAddress> promptForAddress(const QString& label);
std::optional<VirtualMemoryAddress> getSelectedCodeAddress();
bool tryFollowLoadStore(VirtualMemoryAddress address);
void scrollToCodeAddress(VirtualMemoryAddress address);
bool scrollToMemoryAddress(VirtualMemoryAddress address);
void refreshBreakpointList();
Ui::DebuggerWindow m_ui;
std::unique_ptr<DebuggerCodeModel> m_code_model;
std::unique_ptr<DebuggerRegistersModel> m_registers_model;
std::unique_ptr<DebuggerStackModel> m_stack_model;
Bus::MemoryRegion m_active_memory_region;
PhysicalMemoryAddress m_next_memory_search_address = 0;
};

View file

@ -0,0 +1,477 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DebuggerWindow</class>
<widget class="QMainWindow" name="DebuggerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1210</width>
<height>800</height>
</rect>
</property>
<property name="windowTitle">
<string>CPU Debugger</string>
</property>
<property name="dockNestingEnabled">
<bool>true</bool>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1210</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menu_Debugger">
<property name="title">
<string>&amp;Debug</string>
</property>
<addaction name="actionPause"/>
<addaction name="actionRunToCursor"/>
<addaction name="separator"/>
<addaction name="actionGoToPC"/>
<addaction name="actionGoToAddress"/>
<addaction name="actionDumpAddress"/>
<addaction name="separator"/>
<addaction name="actionStepInto"/>
<addaction name="actionStepOver"/>
<addaction name="actionStepOut"/>
<addaction name="separator"/>
<addaction name="actionClose"/>
</widget>
<widget class="QMenu" name="menuBreakpoints">
<property name="title">
<string>Breakpoints</string>
</property>
<addaction name="actionAddBreakpoint"/>
<addaction name="actionToggleBreakpoint"/>
<addaction name="actionClearBreakpoints"/>
</widget>
<addaction name="menu_Debugger"/>
<addaction name="menuBreakpoints"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionPause"/>
<addaction name="actionRunToCursor"/>
<addaction name="separator"/>
<addaction name="actionGoToPC"/>
<addaction name="actionGoToAddress"/>
<addaction name="actionDumpAddress"/>
<addaction name="separator"/>
<addaction name="actionAddBreakpoint"/>
<addaction name="actionToggleBreakpoint"/>
<addaction name="actionClearBreakpoints"/>
<addaction name="separator"/>
<addaction name="actionStepInto"/>
<addaction name="actionStepOver"/>
<addaction name="actionStepOut"/>
</widget>
<widget class="QDockWidget" name="dockWidget_2">
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="windowTitle">
<string>Disassembly</string>
</property>
<attribute name="dockWidgetArea">
<number>4</number>
</attribute>
<widget class="QTreeView" name="codeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_3">
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="windowTitle">
<string>Registers</string>
</property>
<attribute name="dockWidgetArea">
<number>4</number>
</attribute>
<widget class="QTreeView" name="registerView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>220</width>
<height>16777215</height>
</size>
</property>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="windowTitle">
<string>Memory</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_3">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="MemoryViewWidget" name="memoryView" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="memoryRegionRAM">
<property name="text">
<string>RAM</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="memoryRegionScratchpad">
<property name="text">
<string>Scratchpad</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="memoryRegionEXP1">
<property name="text">
<string>EXP1</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="memoryRegionBIOS">
<property name="text">
<string>BIOS</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="memorySearchString"/>
</item>
<item>
<widget class="QPushButton" name="memorySearch">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>524287</height>
</size>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="windowTitle">
<string>Breakpoints</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QTreeWidget" name="breakpointsWidget">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="headerMinimumSectionSize">
<number>20</number>
</attribute>
<column>
<property name="text">
<string>#</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
<column>
<property name="text">
<string>Hit Count</string>
</property>
</column>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_4">
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="windowTitle">
<string>Stack</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QTreeView" name="stackView">
<property name="maximumSize">
<size>
<width>220</width>
<height>16777215</height>
</size>
</property>
</widget>
</widget>
<action name="actionPause">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/media-playback-pause.png</normaloff>:/icons/media-playback-pause.png</iconset>
</property>
<property name="text">
<string>Pause/Continue</string>
</property>
<property name="toolTip">
<string>&amp;Pause/Continue</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</action>
<action name="actionStepInto">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/debug-step-into-instruction.png</normaloff>:/icons/debug-step-into-instruction.png</iconset>
</property>
<property name="text">
<string>Step Into</string>
</property>
<property name="toolTip">
<string>&amp;Step Into</string>
</property>
<property name="shortcut">
<string>F11</string>
</property>
</action>
<action name="actionStepOver">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/debug-step-over.png</normaloff>:/icons/debug-step-over.png</iconset>
</property>
<property name="text">
<string>Step Over</string>
</property>
<property name="toolTip">
<string>Step &amp;Over</string>
</property>
<property name="shortcut">
<string>F10</string>
</property>
</action>
<action name="actionToggleBreakpoint">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/media-record@2x.png</normaloff>:/icons/media-record@2x.png</iconset>
</property>
<property name="text">
<string>Toggle Breakpoint</string>
</property>
<property name="toolTip">
<string>Toggle &amp;Breakpoint</string>
</property>
<property name="shortcut">
<string>F9</string>
</property>
</action>
<action name="actionClose">
<property name="text">
<string>&amp;Close</string>
</property>
</action>
<action name="actionStepOut">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/debug-step-out.png</normaloff>:/icons/debug-step-out.png</iconset>
</property>
<property name="text">
<string>Step Out</string>
</property>
<property name="toolTip">
<string>Step O&amp;ut</string>
</property>
<property name="shortcut">
<string>Ctrl+F11</string>
</property>
</action>
<action name="actionRunToCursor">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/debug-run-cursor.png</normaloff>:/icons/debug-run-cursor.png</iconset>
</property>
<property name="text">
<string>Run To Cursor</string>
</property>
<property name="toolTip">
<string>&amp;Run To Cursor</string>
</property>
<property name="shortcut">
<string>Ctrl+F10</string>
</property>
</action>
<action name="actionClearBreakpoints">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/edit-clear-16.png</normaloff>:/icons/edit-clear-16.png</iconset>
</property>
<property name="text">
<string>Clear Breakpoints</string>
</property>
<property name="toolTip">
<string>&amp;Clear Breakpoints</string>
</property>
<property name="shortcut">
<string>Ctrl+Del</string>
</property>
</action>
<action name="actionAddBreakpoint">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/list-add.png</normaloff>:/icons/list-add.png</iconset>
</property>
<property name="text">
<string>Add Breakpoint</string>
</property>
<property name="toolTip">
<string>Add &amp;Breakpoint</string>
</property>
<property name="shortcut">
<string>Ctrl+F9</string>
</property>
</action>
<action name="actionGoToPC">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/document-open.png</normaloff>:/icons/document-open.png</iconset>
</property>
<property name="text">
<string>Go To PC</string>
</property>
<property name="toolTip">
<string>&amp;Go To PC</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="actionGoToAddress">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/applications-system.png</normaloff>:/icons/applications-system.png</iconset>
</property>
<property name="text">
<string>Go To Address</string>
</property>
<property name="toolTip">
<string>Go To &amp;Address</string>
</property>
<property name="shortcut">
<string>Ctrl+G</string>
</property>
</action>
<action name="actionDumpAddress">
<property name="icon">
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/antialias-icon.png</normaloff>:/icons/antialias-icon.png</iconset>
</property>
<property name="text">
<string>&amp;Dump Address</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>MemoryViewWidget</class>
<extends>QWidget</extends>
<header>memoryviewwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="resources/resources.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -59,6 +59,8 @@
<ClCompile Include="cheatmanagerdialog.cpp" />
<ClCompile Include="cheatcodeeditordialog.cpp" />
<ClCompile Include="consolesettingswidget.cpp" />
<ClCompile Include="debuggermodels.cpp" />
<ClCompile Include="debuggerwindow.cpp" />
<ClCompile Include="enhancementsettingswidget.cpp" />
<ClCompile Include="gamelistmodel.cpp" />
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
@ -68,6 +70,7 @@
<ClCompile Include="inputbindingdialog.cpp" />
<ClCompile Include="inputbindingmonitor.cpp" />
<ClCompile Include="inputbindingwidgets.cpp" />
<ClCompile Include="memoryviewwidget.cpp" />
<ClCompile Include="qtdisplaywidget.cpp" />
<ClCompile Include="gamelistsettingswidget.cpp" />
<ClCompile Include="gamelistwidget.cpp" />
@ -106,7 +109,10 @@
<QtMoc Include="gamelistmodel.h" />
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
<QtMoc Include="autoupdaterdialog.h" />
<QtMoc Include="debuggermodels.h" />
<QtMoc Include="debuggerwindow.h" />
<ClInclude Include="inputbindingmonitor.h" />
<QtMoc Include="memoryviewwidget.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="settingwidgetbinder.h" />
<QtMoc Include="consolesettingswidget.h" />
@ -216,6 +222,8 @@
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamepropertiesdialog.cpp" />
<ClCompile Include="$(IntDir)moc_generalsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_debuggermodels.cpp" />
<ClCompile Include="$(IntDir)moc_debuggerwindow.cpp" />
<ClCompile Include="$(IntDir)moc_displaysettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
@ -223,6 +231,7 @@
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
<ClCompile Include="$(IntDir)moc_memoryviewwidget.cpp" />
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
@ -251,35 +260,48 @@
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<QtUi Include="autoupdaterdialog.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<QtTs Include="translations\duckstation-qt_de.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_es.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_fr.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_he.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_it.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_ja.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_nl.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-br.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_ru.ts">
<FileType>Document</FileType>
</QtTs>
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
<FileType>Document</FileType>
</QtTs>
</ItemGroup>
<ItemGroup>
<QtUi Include="autoupdaterdialog.ui">
<FileType>Document</FileType>
</QtUi>
</ItemGroup>
<ItemGroup>
<None Include="translations\duckstation-qt_es.ts" />
<None Include="translations\duckstation-qt_fr.ts" />
<None Include="translations\duckstation-qt_it.ts" />
<None Include="translations\duckstation-qt_nl.ts" />
<None Include="translations\duckstation-qt_ru.ts" />
<None Include="translations\duckstation-qt_ja.ts" />
<None Include="debuggerwindow.ui" />
</ItemGroup>
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
<Message Text="Copying common data files" Importance="High" />

View file

@ -65,6 +65,12 @@
<ClCompile Include="cheatcodeeditordialog.cpp" />
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp" />
<ClCompile Include="debuggerwindow.cpp" />
<ClCompile Include="debuggermodels.cpp" />
<ClCompile Include="memoryviewwidget.cpp" />
<ClCompile Include="$(IntDir)moc_debuggerdialog.cpp" />
<ClCompile Include="$(IntDir)moc_debuggermodels.cpp" />
<ClCompile Include="$(IntDir)moc_memoryviewwidget.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="qtutils.h" />
@ -111,6 +117,9 @@
<QtMoc Include="postprocessingsettingswidget.h" />
<QtMoc Include="cheatmanagerdialog.h" />
<QtMoc Include="cheatcodeeditordialog.h" />
<QtMoc Include="debuggermodels.h" />
<QtMoc Include="debuggerwindow.h" />
<QtMoc Include="memoryviewwidget.h" />
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui" />
@ -142,46 +151,47 @@
<ItemGroup>
<Image Include="duckstation-qt.ico" />
</ItemGroup>
<ItemGroup>
<QtResource Include="resources\resources.qrc">
<Filter>resources</Filter>
</QtResource>
</ItemGroup>
<ItemGroup>
<None Include="debuggerwindow.ui" />
</ItemGroup>
<ItemGroup>
<QtTs Include="translations\duckstation-qt_de.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_es.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_fr.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_he.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_it.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_ja.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_nl.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-br.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_ru.ts">
<Filter>translations</Filter>
</QtTs>
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
<Filter>translations</Filter>
</QtTs>
</ItemGroup>
<ItemGroup>
<None Include="translations\duckstation-qt_es.ts">
<Filter>translations</Filter>
</None>
<None Include="translations\duckstation-qt_it.ts">
<Filter>translations</Filter>
</None>
<None Include="translations\duckstation-qt_ru.ts">
<Filter>translations</Filter>
</None>
<None Include="translations\duckstation-qt_nl.ts">
<Filter>translations</Filter>
</None>
<None Include="translations\duckstation-qt_fr.ts">
<Filter>translations</Filter>
</None>
<None Include="translations\duckstation-qt_ja.ts">
<Filter>translations</Filter>
</None>
</ItemGroup>
<ItemGroup>
<QtResource Include="resources\resources.qrc">
<Filter>resources</Filter>
</QtResource>
</ItemGroup>
</Project>

View file

@ -6,6 +6,7 @@
#include "core/host_display.h"
#include "core/settings.h"
#include "core/system.h"
#include "debuggerwindow.h"
#include "frontend-common/game_list.h"
#include "gamelistsettingswidget.h"
#include "gamelistwidget.h"
@ -60,6 +61,8 @@ MainWindow::~MainWindow()
{
Assert(!m_display_widget);
m_host_interface->setMainWindow(nullptr);
Assert(!m_debugger_window);
}
void MainWindow::reportError(const QString& message)
@ -311,6 +314,12 @@ void MainWindow::onEmulationStopped()
delete m_cheat_manager_dialog;
m_cheat_manager_dialog = nullptr;
}
if (m_debugger_window)
{
delete m_debugger_window;
m_debugger_window = nullptr;
}
}
void MainWindow::onEmulationPaused(bool paused)
@ -761,6 +770,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.menuChangeDisc->setDisabled(starting || !running);
m_ui.menuCheats->setDisabled(starting || !running);
m_ui.actionCheatManager->setDisabled(starting || !running);
m_ui.actionCPUDebugger->setDisabled(starting || !running);
m_ui.actionSaveState->setDisabled(starting || !running);
m_ui.menuSaveState->setDisabled(starting || !running);
@ -850,7 +860,7 @@ void MainWindow::connectSignals()
[this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
connect(m_ui.actionPowerOff, &QAction::triggered, m_host_interface, &QtHostInterface::powerOffSystem);
connect(m_ui.actionReset, &QAction::triggered, m_host_interface, &QtHostInterface::resetSystem);
connect(m_ui.actionPause, &QAction::toggled, m_host_interface, &QtHostInterface::pauseSystem);
connect(m_ui.actionPause, &QAction::toggled, [this](bool active) { m_host_interface->pauseSystem(active); });
connect(m_ui.actionScreenshot, &QAction::triggered, m_host_interface, &QtHostInterface::saveScreenshot);
connect(m_ui.actionScanForNewGames, &QAction::triggered, this,
[this]() { m_host_interface->refreshGameList(false, false); });
@ -899,6 +909,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
connect(m_ui.actionCheatManager, &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
connect(m_ui.actionCPUDebugger, &QAction::triggered, this, &MainWindow::onToolsCPUDebuggerTriggered);
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
@ -1325,6 +1336,26 @@ void MainWindow::onToolsCheatManagerTriggered()
m_cheat_manager_dialog->show();
}
void MainWindow::onToolsCPUDebuggerTriggered()
{
if (!m_debugger_window)
{
m_debugger_window = new DebuggerWindow();
m_debugger_window->setWindowIcon(windowIcon());
connect(m_debugger_window, &DebuggerWindow::closed, this, &MainWindow::onCPUDebuggerClosed);
}
m_debugger_window->show();
m_host_interface->pauseSystem(true);
}
void MainWindow::onCPUDebuggerClosed()
{
Assert(m_debugger_window);
m_debugger_window->deleteLater();
m_debugger_window = nullptr;
}
void MainWindow::onToolsOpenDataDirectoryTriggered()
{
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QString())));

View file

@ -16,6 +16,7 @@ class QtDisplayWidget;
class AutoUpdaterDialog;
class MemoryCardEditorDialog;
class CheatManagerDialog;
class DebuggerWindow;
class HostDisplay;
struct GameListEntry;
@ -83,12 +84,14 @@ private Q_SLOTS:
void onCheckForUpdatesActionTriggered();
void onToolsMemoryCardEditorTriggered();
void onToolsCheatManagerTriggered();
void onToolsCPUDebuggerTriggered();
void onToolsOpenDataDirectoryTriggered();
void onGameListEntrySelected(const GameListEntry* entry);
void onGameListEntryDoubleClicked(const GameListEntry* entry);
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
void onGameListSetCoverImageRequested(const GameListEntry* entry);
void onCPUDebuggerClosed();
void checkForUpdates(bool display_message);
void onUpdateCheckComplete();
@ -137,6 +140,7 @@ private:
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
DebuggerWindow* m_debugger_window = nullptr;
bool m_emulation_running = false;
bool m_was_paused_by_focus_loss = false;

View file

@ -176,6 +176,8 @@
<addaction name="actionDebugDumpCPUtoVRAMCopies"/>
<addaction name="actionDebugDumpVRAMtoCPUCopies"/>
<addaction name="actionDumpAudio"/>
<addaction name="separator"/>
<addaction name="actionCPUDebugger" />
<addaction name="actionDumpRAM"/>
<addaction name="separator"/>
<addaction name="actionDebugShowVRAM"/>
@ -736,6 +738,11 @@
<string>C&amp;heat Manager</string>
</property>
</action>
<action name="actionCPUDebugger">
<property name="text">
<string>CPU D&amp;ebugger</string>
</property>
</action>
<action name="actionViewGameGrid">
<property name="text">
<string>Game &amp;Grid</string>

View file

@ -0,0 +1,234 @@
#include "memoryviewwidget.h"
#include <QtGui/QPainter>
#include <QtWidgets/QScrollBar>
MemoryViewWidget::MemoryViewWidget(QWidget* parent /* = nullptr */, size_t address_offset /* = 0 */,
const void* data_ptr /* = nullptr */, size_t data_size /* = 0 */)
: QAbstractScrollArea(parent)
{
m_bytes_per_line = 16;
updateMetrics();
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &MemoryViewWidget::adjustContent);
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &MemoryViewWidget::adjustContent);
if (data_ptr)
setData(address_offset, data_ptr, data_size);
}
MemoryViewWidget::~MemoryViewWidget() = default;
int MemoryViewWidget::addressWidth() const
{
return (8 * m_char_width) + m_char_width;
}
int MemoryViewWidget::hexWidth() const
{
return (m_bytes_per_line * 4) * m_char_width;
}
int MemoryViewWidget::asciiWidth() const
{
return (m_bytes_per_line * 2 + 1) * m_char_width;
}
void MemoryViewWidget::updateMetrics()
{
m_char_width = fontMetrics().horizontalAdvance(QChar('0'));
m_char_height = fontMetrics().height();
}
void MemoryViewWidget::setData(size_t address_offset, const void* data_ptr, size_t data_size)
{
m_data = data_ptr;
m_data_size = data_size;
m_address_offset = address_offset;
adjustContent();
}
void MemoryViewWidget::setHighlightRange(size_t start, size_t end)
{
m_highlight_start = start;
m_highlight_end = end;
viewport()->update();
}
void MemoryViewWidget::clearHighlightRange()
{
m_highlight_start = 0;
m_highlight_end = 0;
viewport()->update();
}
void MemoryViewWidget::scrolltoOffset(size_t offset)
{
const unsigned row = static_cast<unsigned>(offset / m_bytes_per_line);
verticalScrollBar()->setSliderPosition(static_cast<int>(row));
horizontalScrollBar()->setSliderPosition(0);
}
void MemoryViewWidget::scrollToAddress(size_t address)
{
const unsigned row = static_cast<unsigned>((address - m_start_offset) / m_bytes_per_line);
verticalScrollBar()->setSliderPosition(static_cast<int>(row));
horizontalScrollBar()->setSliderPosition(0);
}
void MemoryViewWidget::setFont(const QFont& font)
{
QAbstractScrollArea::setFont(font);
updateMetrics();
}
void MemoryViewWidget::resizeEvent(QResizeEvent*)
{
adjustContent();
}
template<typename T>
static bool RangesOverlap(T x1, T x2, T y1, T y2)
{
return (x2 >= y1 && x1 < y2);
}
void MemoryViewWidget::paintEvent(QPaintEvent*)
{
QPainter painter(viewport());
painter.setFont(font());
if (!m_data)
return;
const QColor highlight_color(100, 100, 0);
const int offsetX = horizontalScrollBar()->value();
int y = m_char_height;
QString address;
painter.setPen(viewport()->palette().color(QPalette::WindowText));
y += m_char_height;
const unsigned num_rows = static_cast<unsigned>(m_end_offset - m_start_offset) / m_bytes_per_line;
for (unsigned row = 0; row <= num_rows; row++)
{
const size_t data_offset = m_start_offset + (row * m_bytes_per_line);
const unsigned row_address = static_cast<unsigned>(m_address_offset + data_offset);
const int draw_x = m_char_width / 2 - offsetX;
if (RangesOverlap(data_offset, data_offset + m_bytes_per_line, m_highlight_start, m_highlight_end))
painter.fillRect(0, y - m_char_height + 3, addressWidth(), m_char_height, highlight_color);
const QString address_text(QString::asprintf("%08X", row_address));
painter.drawText(draw_x, y, address_text);
y += m_char_height;
}
int x;
int lx = addressWidth();
painter.drawLine(lx - offsetX, 0, lx - offsetX, height());
y = m_char_height;
// hex data
const int HEX_CHAR_WIDTH = 4 * m_char_width;
x = lx - offsetX;
for (unsigned col = 0; col < m_bytes_per_line; col++)
{
if ((col % 2) != 0)
painter.fillRect(x, 0, HEX_CHAR_WIDTH, height(), viewport()->palette().color(QPalette::AlternateBase));
x += HEX_CHAR_WIDTH;
}
y = m_char_height;
x = lx - offsetX + m_char_width;
for (unsigned col = 0; col < m_bytes_per_line; col++)
{
painter.drawText(x, y, QString::asprintf("%02X", col));
x += HEX_CHAR_WIDTH;
}
painter.drawLine(0, y + 3, width(), y + 3);
y += m_char_height;
size_t offset = m_start_offset;
for (unsigned row = 0; row <= num_rows; row++)
{
x = lx - offsetX + m_char_width;
for (unsigned col = 0; col < m_bytes_per_line && offset < m_data_size; col++, offset++)
{
unsigned char value;
std::memcpy(&value, static_cast<const unsigned char*>(m_data) + offset, sizeof(value));
if (offset >= m_highlight_start && offset < m_highlight_end)
painter.fillRect(x - m_char_width, y - m_char_height + 3, HEX_CHAR_WIDTH, m_char_height, highlight_color);
painter.drawText(x, y, QString::asprintf("%02X", value));
x += HEX_CHAR_WIDTH;
}
y += m_char_height;
}
lx = addressWidth() + hexWidth();
painter.drawLine(lx - offsetX, 0, lx - offsetX, height());
lx += m_char_width;
y = m_char_height;
x = (lx - offsetX);
for (unsigned col = 0; col < m_bytes_per_line; col++)
{
const QChar ch = (col < 0xA) ? (static_cast<QChar>('0' + col)) : (static_cast<QChar>('A' + (col - 0xA)));
painter.drawText(x, y, ch);
x += 2 * m_char_width;
}
y += m_char_height;
offset = m_start_offset;
for (unsigned row = 0; row <= num_rows; row++)
{
x = lx - offsetX;
for (unsigned col = 0; col < m_bytes_per_line && offset < m_data_size; col++, offset++)
{
unsigned char value;
std::memcpy(&value, static_cast<const unsigned char*>(m_data) + offset, sizeof(value));
if (offset >= m_highlight_start && offset < m_highlight_end)
painter.fillRect(x, y - m_char_height + 3, 2 * m_char_width, m_char_height, highlight_color);
if (!std::isprint(value))
value = '.';
painter.drawText(x, y, static_cast<QChar>(value));
x += 2 * m_char_width;
}
y += m_char_height;
}
}
void MemoryViewWidget::adjustContent()
{
if (!m_data)
{
setEnabled(false);
return;
}
setEnabled(true);
int w = addressWidth() + hexWidth() + asciiWidth();
horizontalScrollBar()->setRange(0, w - viewport()->width());
horizontalScrollBar()->setPageStep(viewport()->width());
m_rows_visible = viewport()->height() / m_char_height;
int val = verticalScrollBar()->value();
m_start_offset = (size_t)val * m_bytes_per_line;
m_end_offset = m_start_offset + m_rows_visible * m_bytes_per_line - 1;
if (m_end_offset >= m_data_size)
m_end_offset = m_data_size - 1;
const int lineCount = static_cast<int>(m_data_size / m_bytes_per_line);
verticalScrollBar()->setRange(0, lineCount - m_rows_visible);
verticalScrollBar()->setPageStep(m_rows_visible);
viewport()->update();
}

View file

@ -0,0 +1,53 @@
#pragma once
#include <QtWidgets/QAbstractScrollArea>
// Based on https://stackoverflow.com/questions/46375673/how-can-realize-my-own-memory-viewer-by-qt
class MemoryViewWidget : public QAbstractScrollArea
{
public:
Q_OBJECT
public:
MemoryViewWidget(QWidget* parent = nullptr, size_t address_offset = 0, const void* data_ptr = nullptr,
size_t data_size = 0);
~MemoryViewWidget();
size_t addressOffset() const { return m_address_offset; }
void setData(size_t address_offset, const void* data_ptr, size_t data_size);
void setHighlightRange(size_t start, size_t end);
void clearHighlightRange();
void scrolltoOffset(size_t offset);
void scrollToAddress(size_t address);
void setFont(const QFont& font);
protected:
void paintEvent(QPaintEvent*);
void resizeEvent(QResizeEvent*);
private Q_SLOTS:
void adjustContent();
private:
int addressWidth() const;
int hexWidth() const;
int asciiWidth() const;
void updateMetrics();
const void* m_data;
size_t m_data_size;
size_t m_address_offset;
size_t m_start_offset;
size_t m_end_offset;
size_t m_highlight_start = 0;
size_t m_highlight_end = 0;
unsigned m_bytes_per_line;
int m_char_width;
int m_char_height;
int m_rows_visible;
};

View file

@ -821,11 +821,13 @@ void QtHostInterface::resetSystem()
HostInterface::ResetSystem();
}
void QtHostInterface::pauseSystem(bool paused)
void QtHostInterface::pauseSystem(bool paused, bool wait_until_paused /* = false */)
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "pauseSystem", Qt::QueuedConnection, Q_ARG(bool, paused));
QMetaObject::invokeMethod(this, "pauseSystem",
wait_until_paused ? Qt::BlockingQueuedConnection : Qt::QueuedConnection,
Q_ARG(bool, paused), Q_ARG(bool, wait_until_paused));
return;
}
@ -1222,6 +1224,21 @@ void QtHostInterface::stopDumpingAudio()
StopDumpingAudio();
}
void QtHostInterface::singleStepCPU()
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "singleStepCPU", Qt::BlockingQueuedConnection);
return;
}
if (!System::IsValid())
return;
System::SingleStepCPU();
renderDisplay();
}
void QtHostInterface::dumpRAM(const QString& filename)
{
if (!isOnWorkerThread())

View file

@ -155,7 +155,7 @@ public Q_SLOTS:
void powerOffSystem();
void synchronousPowerOffSystem();
void resetSystem();
void pauseSystem(bool paused);
void pauseSystem(bool paused, bool wait_until_paused = false);
void changeDisc(const QString& new_disc_filename);
void changeDiscFromPlaylist(quint32 index);
void loadState(const QString& filename);
@ -165,6 +165,7 @@ public Q_SLOTS:
void setAudioOutputMuted(bool muted);
void startDumpingAudio();
void stopDumpingAudio();
void singleStepCPU();
void dumpRAM(const QString& filename);
void saveScreenshot();
void redrawDisplayWindow();

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

View file

@ -24,6 +24,17 @@
<file>icons/conical-flask-red.png</file>
<file>icons/conical-flask-red@2x.png</file>
<file>icons/cover-placeholder.png</file>
<file>icons/debug-execute-from-cursor.png</file>
<file>icons/debug-execute-to-cursor.png</file>
<file>icons/debug-run-cursor.png</file>
<file>icons/debug-run.png</file>
<file>icons/debug-pc.png</file>
<file>icons/debug-pc@2x.png</file>
<file>icons/debug-step-instruction.png</file>
<file>icons/debug-step-into-instruction.png</file>
<file>icons/debug-step-into.png</file>
<file>icons/debug-step-out.png</file>
<file>icons/debug-step-over.png</file>
<file>icons/document-open.png</file>
<file>icons/document-open@2x.png</file>
<file>icons/document-save.png</file>