Qt: Add debugger UI
|
@ -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)).
|
||||
|
|
|
@ -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
|
||||
|
|
417
src/duckstation-qt/debuggermodels.cpp
Normal 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();
|
||||
}
|
84
src/duckstation-qt/debuggermodels.h
Normal 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();
|
||||
};
|
597
src/duckstation-qt/debuggerwindow.cpp
Normal 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);
|
||||
}
|
||||
}
|
79
src/duckstation-qt/debuggerwindow.h
Normal 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;
|
||||
};
|
477
src/duckstation-qt/debuggerwindow.ui
Normal 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>&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>&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>&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 &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 &Breakpoint</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F9</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionClose">
|
||||
<property name="text">
|
||||
<string>&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&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>&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>&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 &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>&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 &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>&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>
|
|
@ -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" />
|
||||
|
@ -778,4 +800,4 @@
|
|||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\dep\msvc\vsprops\QtCompile.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -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>
|
|
@ -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())));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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&heat Manager</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCPUDebugger">
|
||||
<property name="text">
|
||||
<string>CPU D&ebugger</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewGameGrid">
|
||||
<property name="text">
|
||||
<string>Game &Grid</string>
|
||||
|
|
234
src/duckstation-qt/memoryviewwidget.cpp
Normal 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();
|
||||
}
|
53
src/duckstation-qt/memoryviewwidget.h
Normal 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;
|
||||
};
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
BIN
src/duckstation-qt/resources/icons/debug-execute-from-cursor.png
Normal file
After Width: | Height: | Size: 423 B |
BIN
src/duckstation-qt/resources/icons/debug-execute-to-cursor.png
Normal file
After Width: | Height: | Size: 437 B |
BIN
src/duckstation-qt/resources/icons/debug-pc.png
Normal file
After Width: | Height: | Size: 805 B |
BIN
src/duckstation-qt/resources/icons/debug-pc@2x.png
Normal file
After Width: | Height: | Size: 707 B |
BIN
src/duckstation-qt/resources/icons/debug-run-cursor.png
Normal file
After Width: | Height: | Size: 651 B |
BIN
src/duckstation-qt/resources/icons/debug-run.png
Normal file
After Width: | Height: | Size: 817 B |
BIN
src/duckstation-qt/resources/icons/debug-step-instruction.png
Normal file
After Width: | Height: | Size: 659 B |
After Width: | Height: | Size: 669 B |
BIN
src/duckstation-qt/resources/icons/debug-step-into.png
Normal file
After Width: | Height: | Size: 689 B |
BIN
src/duckstation-qt/resources/icons/debug-step-out.png
Normal file
After Width: | Height: | Size: 631 B |
BIN
src/duckstation-qt/resources/icons/debug-step-over.png
Normal file
After Width: | Height: | Size: 679 B |
|
@ -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>
|
||||
|
|