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
|
## 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/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/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)).
|
- 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
|
consolesettingswidget.ui
|
||||||
controllersettingswidget.cpp
|
controllersettingswidget.cpp
|
||||||
controllersettingswidget.h
|
controllersettingswidget.h
|
||||||
|
debuggermodels.cpp
|
||||||
|
debuggermodels.h
|
||||||
|
debuggerwindow.cpp
|
||||||
|
debuggerwindow.h
|
||||||
|
debuggerwindow.ui
|
||||||
displaysettingswidget.cpp
|
displaysettingswidget.cpp
|
||||||
displaysettingswidget.h
|
displaysettingswidget.h
|
||||||
displaysettingswidget.ui
|
displaysettingswidget.ui
|
||||||
|
@ -68,6 +73,8 @@ set(SRCS
|
||||||
memorycardeditordialog.ui
|
memorycardeditordialog.ui
|
||||||
memorycardsettingswidget.cpp
|
memorycardsettingswidget.cpp
|
||||||
memorycardsettingswidget.h
|
memorycardsettingswidget.h
|
||||||
|
memoryviewwidget.cpp
|
||||||
|
memoryviewwidget.h
|
||||||
postprocessingchainconfigwidget.cpp
|
postprocessingchainconfigwidget.cpp
|
||||||
postprocessingchainconfigwidget.h
|
postprocessingchainconfigwidget.h
|
||||||
postprocessingchainconfigwidget.ui
|
postprocessingchainconfigwidget.ui
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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="cheatmanagerdialog.cpp" />
|
||||||
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
||||||
<ClCompile Include="consolesettingswidget.cpp" />
|
<ClCompile Include="consolesettingswidget.cpp" />
|
||||||
|
<ClCompile Include="debuggermodels.cpp" />
|
||||||
|
<ClCompile Include="debuggerwindow.cpp" />
|
||||||
<ClCompile Include="enhancementsettingswidget.cpp" />
|
<ClCompile Include="enhancementsettingswidget.cpp" />
|
||||||
<ClCompile Include="gamelistmodel.cpp" />
|
<ClCompile Include="gamelistmodel.cpp" />
|
||||||
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
<ClCompile Include="gamelistsearchdirectoriesmodel.cpp" />
|
||||||
|
@ -68,6 +70,7 @@
|
||||||
<ClCompile Include="inputbindingdialog.cpp" />
|
<ClCompile Include="inputbindingdialog.cpp" />
|
||||||
<ClCompile Include="inputbindingmonitor.cpp" />
|
<ClCompile Include="inputbindingmonitor.cpp" />
|
||||||
<ClCompile Include="inputbindingwidgets.cpp" />
|
<ClCompile Include="inputbindingwidgets.cpp" />
|
||||||
|
<ClCompile Include="memoryviewwidget.cpp" />
|
||||||
<ClCompile Include="qtdisplaywidget.cpp" />
|
<ClCompile Include="qtdisplaywidget.cpp" />
|
||||||
<ClCompile Include="gamelistsettingswidget.cpp" />
|
<ClCompile Include="gamelistsettingswidget.cpp" />
|
||||||
<ClCompile Include="gamelistwidget.cpp" />
|
<ClCompile Include="gamelistwidget.cpp" />
|
||||||
|
@ -106,7 +109,10 @@
|
||||||
<QtMoc Include="gamelistmodel.h" />
|
<QtMoc Include="gamelistmodel.h" />
|
||||||
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
<QtMoc Include="gamelistsearchdirectoriesmodel.h" />
|
||||||
<QtMoc Include="autoupdaterdialog.h" />
|
<QtMoc Include="autoupdaterdialog.h" />
|
||||||
|
<QtMoc Include="debuggermodels.h" />
|
||||||
|
<QtMoc Include="debuggerwindow.h" />
|
||||||
<ClInclude Include="inputbindingmonitor.h" />
|
<ClInclude Include="inputbindingmonitor.h" />
|
||||||
|
<QtMoc Include="memoryviewwidget.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="settingwidgetbinder.h" />
|
<ClInclude Include="settingwidgetbinder.h" />
|
||||||
<QtMoc Include="consolesettingswidget.h" />
|
<QtMoc Include="consolesettingswidget.h" />
|
||||||
|
@ -216,6 +222,8 @@
|
||||||
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_gamelistwidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_gamepropertiesdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_gamepropertiesdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_generalsettingswidget.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_displaysettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
||||||
|
@ -223,6 +231,7 @@
|
||||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_memoryviewwidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
|
||||||
|
@ -251,35 +260,48 @@
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<QtUi Include="autoupdaterdialog.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtTs Include="translations\duckstation-qt_de.ts">
|
<QtTs Include="translations\duckstation-qt_de.ts">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtTs>
|
</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">
|
<QtTs Include="translations\duckstation-qt_he.ts">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtTs>
|
</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">
|
<QtTs Include="translations\duckstation-qt_pt-br.ts">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
|
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
|
<QtTs Include="translations\duckstation-qt_ru.ts">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtTs>
|
||||||
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
|
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtUi Include="autoupdaterdialog.ui">
|
<None Include="debuggerwindow.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" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
|
<Target Name="CopyCommonDataFiles" AfterTargets="Build" Inputs="@(CommonDataFiles)" Outputs="@(CommonDataFiles -> '$(BinaryOutputDir)%(RecursiveDir)%(Filename)%(Extension)')">
|
||||||
<Message Text="Copying common data files" Importance="High" />
|
<Message Text="Copying common data files" Importance="High" />
|
||||||
|
|
|
@ -65,6 +65,12 @@
|
||||||
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="qtutils.h" />
|
<ClInclude Include="qtutils.h" />
|
||||||
|
@ -111,6 +117,9 @@
|
||||||
<QtMoc Include="postprocessingsettingswidget.h" />
|
<QtMoc Include="postprocessingsettingswidget.h" />
|
||||||
<QtMoc Include="cheatmanagerdialog.h" />
|
<QtMoc Include="cheatmanagerdialog.h" />
|
||||||
<QtMoc Include="cheatcodeeditordialog.h" />
|
<QtMoc Include="cheatcodeeditordialog.h" />
|
||||||
|
<QtMoc Include="debuggermodels.h" />
|
||||||
|
<QtMoc Include="debuggerwindow.h" />
|
||||||
|
<QtMoc Include="memoryviewwidget.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtUi Include="consolesettingswidget.ui" />
|
<QtUi Include="consolesettingswidget.ui" />
|
||||||
|
@ -142,46 +151,47 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="duckstation-qt.ico" />
|
<Image Include="duckstation-qt.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<QtResource Include="resources\resources.qrc">
|
||||||
|
<Filter>resources</Filter>
|
||||||
|
</QtResource>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="debuggerwindow.ui" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtTs Include="translations\duckstation-qt_de.ts">
|
<QtTs Include="translations\duckstation-qt_de.ts">
|
||||||
<Filter>translations</Filter>
|
<Filter>translations</Filter>
|
||||||
</QtTs>
|
</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">
|
<QtTs Include="translations\duckstation-qt_he.ts">
|
||||||
<Filter>translations</Filter>
|
<Filter>translations</Filter>
|
||||||
</QtTs>
|
</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">
|
<QtTs Include="translations\duckstation-qt_pt-br.ts">
|
||||||
<Filter>translations</Filter>
|
<Filter>translations</Filter>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
|
<QtTs Include="translations\duckstation-qt_pt-pt.ts">
|
||||||
<Filter>translations</Filter>
|
<Filter>translations</Filter>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
|
<QtTs Include="translations\duckstation-qt_ru.ts">
|
||||||
|
<Filter>translations</Filter>
|
||||||
|
</QtTs>
|
||||||
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
|
<QtTs Include="translations\duckstation-qt_zh-cn.ts">
|
||||||
<Filter>translations</Filter>
|
<Filter>translations</Filter>
|
||||||
</QtTs>
|
</QtTs>
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
|
@ -6,6 +6,7 @@
|
||||||
#include "core/host_display.h"
|
#include "core/host_display.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
#include "debuggerwindow.h"
|
||||||
#include "frontend-common/game_list.h"
|
#include "frontend-common/game_list.h"
|
||||||
#include "gamelistsettingswidget.h"
|
#include "gamelistsettingswidget.h"
|
||||||
#include "gamelistwidget.h"
|
#include "gamelistwidget.h"
|
||||||
|
@ -60,6 +61,8 @@ MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
Assert(!m_display_widget);
|
Assert(!m_display_widget);
|
||||||
m_host_interface->setMainWindow(nullptr);
|
m_host_interface->setMainWindow(nullptr);
|
||||||
|
|
||||||
|
Assert(!m_debugger_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::reportError(const QString& message)
|
void MainWindow::reportError(const QString& message)
|
||||||
|
@ -311,6 +314,12 @@ void MainWindow::onEmulationStopped()
|
||||||
delete m_cheat_manager_dialog;
|
delete m_cheat_manager_dialog;
|
||||||
m_cheat_manager_dialog = nullptr;
|
m_cheat_manager_dialog = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_debugger_window)
|
||||||
|
{
|
||||||
|
delete m_debugger_window;
|
||||||
|
m_debugger_window = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onEmulationPaused(bool paused)
|
void MainWindow::onEmulationPaused(bool paused)
|
||||||
|
@ -761,6 +770,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
|
||||||
m_ui.menuChangeDisc->setDisabled(starting || !running);
|
m_ui.menuChangeDisc->setDisabled(starting || !running);
|
||||||
m_ui.menuCheats->setDisabled(starting || !running);
|
m_ui.menuCheats->setDisabled(starting || !running);
|
||||||
m_ui.actionCheatManager->setDisabled(starting || !running);
|
m_ui.actionCheatManager->setDisabled(starting || !running);
|
||||||
|
m_ui.actionCPUDebugger->setDisabled(starting || !running);
|
||||||
|
|
||||||
m_ui.actionSaveState->setDisabled(starting || !running);
|
m_ui.actionSaveState->setDisabled(starting || !running);
|
||||||
m_ui.menuSaveState->setDisabled(starting || !running);
|
m_ui.menuSaveState->setDisabled(starting || !running);
|
||||||
|
@ -850,7 +860,7 @@ void MainWindow::connectSignals()
|
||||||
[this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
|
[this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
|
||||||
connect(m_ui.actionPowerOff, &QAction::triggered, m_host_interface, &QtHostInterface::powerOffSystem);
|
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.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.actionScreenshot, &QAction::triggered, m_host_interface, &QtHostInterface::saveScreenshot);
|
||||||
connect(m_ui.actionScanForNewGames, &QAction::triggered, this,
|
connect(m_ui.actionScanForNewGames, &QAction::triggered, this,
|
||||||
[this]() { m_host_interface->refreshGameList(false, false); });
|
[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.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
|
||||||
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
|
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
|
||||||
connect(m_ui.actionCheatManager, &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
|
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.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||||
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
||||||
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
||||||
|
@ -1325,6 +1336,26 @@ void MainWindow::onToolsCheatManagerTriggered()
|
||||||
m_cheat_manager_dialog->show();
|
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()
|
void MainWindow::onToolsOpenDataDirectoryTriggered()
|
||||||
{
|
{
|
||||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QString())));
|
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QString())));
|
||||||
|
|
|
@ -16,6 +16,7 @@ class QtDisplayWidget;
|
||||||
class AutoUpdaterDialog;
|
class AutoUpdaterDialog;
|
||||||
class MemoryCardEditorDialog;
|
class MemoryCardEditorDialog;
|
||||||
class CheatManagerDialog;
|
class CheatManagerDialog;
|
||||||
|
class DebuggerWindow;
|
||||||
|
|
||||||
class HostDisplay;
|
class HostDisplay;
|
||||||
struct GameListEntry;
|
struct GameListEntry;
|
||||||
|
@ -83,12 +84,14 @@ private Q_SLOTS:
|
||||||
void onCheckForUpdatesActionTriggered();
|
void onCheckForUpdatesActionTriggered();
|
||||||
void onToolsMemoryCardEditorTriggered();
|
void onToolsMemoryCardEditorTriggered();
|
||||||
void onToolsCheatManagerTriggered();
|
void onToolsCheatManagerTriggered();
|
||||||
|
void onToolsCPUDebuggerTriggered();
|
||||||
void onToolsOpenDataDirectoryTriggered();
|
void onToolsOpenDataDirectoryTriggered();
|
||||||
|
|
||||||
void onGameListEntrySelected(const GameListEntry* entry);
|
void onGameListEntrySelected(const GameListEntry* entry);
|
||||||
void onGameListEntryDoubleClicked(const GameListEntry* entry);
|
void onGameListEntryDoubleClicked(const GameListEntry* entry);
|
||||||
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
void onGameListContextMenuRequested(const QPoint& point, const GameListEntry* entry);
|
||||||
void onGameListSetCoverImageRequested(const GameListEntry* entry);
|
void onGameListSetCoverImageRequested(const GameListEntry* entry);
|
||||||
|
void onCPUDebuggerClosed();
|
||||||
|
|
||||||
void checkForUpdates(bool display_message);
|
void checkForUpdates(bool display_message);
|
||||||
void onUpdateCheckComplete();
|
void onUpdateCheckComplete();
|
||||||
|
@ -137,6 +140,7 @@ private:
|
||||||
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||||
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
||||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||||
|
DebuggerWindow* m_debugger_window = nullptr;
|
||||||
|
|
||||||
bool m_emulation_running = false;
|
bool m_emulation_running = false;
|
||||||
bool m_was_paused_by_focus_loss = false;
|
bool m_was_paused_by_focus_loss = false;
|
||||||
|
|
|
@ -176,6 +176,8 @@
|
||||||
<addaction name="actionDebugDumpCPUtoVRAMCopies"/>
|
<addaction name="actionDebugDumpCPUtoVRAMCopies"/>
|
||||||
<addaction name="actionDebugDumpVRAMtoCPUCopies"/>
|
<addaction name="actionDebugDumpVRAMtoCPUCopies"/>
|
||||||
<addaction name="actionDumpAudio"/>
|
<addaction name="actionDumpAudio"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionCPUDebugger" />
|
||||||
<addaction name="actionDumpRAM"/>
|
<addaction name="actionDumpRAM"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionDebugShowVRAM"/>
|
<addaction name="actionDebugShowVRAM"/>
|
||||||
|
@ -736,6 +738,11 @@
|
||||||
<string>C&heat Manager</string>
|
<string>C&heat Manager</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionCPUDebugger">
|
||||||
|
<property name="text">
|
||||||
|
<string>CPU D&ebugger</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="actionViewGameGrid">
|
<action name="actionViewGameGrid">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Game &Grid</string>
|
<string>Game &Grid</string>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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();
|
HostInterface::ResetSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtHostInterface::pauseSystem(bool paused)
|
void QtHostInterface::pauseSystem(bool paused, bool wait_until_paused /* = false */)
|
||||||
{
|
{
|
||||||
if (!isOnWorkerThread())
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1222,6 +1224,21 @@ void QtHostInterface::stopDumpingAudio()
|
||||||
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)
|
void QtHostInterface::dumpRAM(const QString& filename)
|
||||||
{
|
{
|
||||||
if (!isOnWorkerThread())
|
if (!isOnWorkerThread())
|
||||||
|
|
|
@ -155,7 +155,7 @@ public Q_SLOTS:
|
||||||
void powerOffSystem();
|
void powerOffSystem();
|
||||||
void synchronousPowerOffSystem();
|
void synchronousPowerOffSystem();
|
||||||
void resetSystem();
|
void resetSystem();
|
||||||
void pauseSystem(bool paused);
|
void pauseSystem(bool paused, bool wait_until_paused = false);
|
||||||
void changeDisc(const QString& new_disc_filename);
|
void changeDisc(const QString& new_disc_filename);
|
||||||
void changeDiscFromPlaylist(quint32 index);
|
void changeDiscFromPlaylist(quint32 index);
|
||||||
void loadState(const QString& filename);
|
void loadState(const QString& filename);
|
||||||
|
@ -165,6 +165,7 @@ public Q_SLOTS:
|
||||||
void setAudioOutputMuted(bool muted);
|
void setAudioOutputMuted(bool muted);
|
||||||
void startDumpingAudio();
|
void startDumpingAudio();
|
||||||
void stopDumpingAudio();
|
void stopDumpingAudio();
|
||||||
|
void singleStepCPU();
|
||||||
void dumpRAM(const QString& filename);
|
void dumpRAM(const QString& filename);
|
||||||
void saveScreenshot();
|
void saveScreenshot();
|
||||||
void redrawDisplayWindow();
|
void redrawDisplayWindow();
|
||||||
|
|
After Width: | Height: | Size: 423 B |
After Width: | Height: | Size: 437 B |
After Width: | Height: | Size: 805 B |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 817 B |
After Width: | Height: | Size: 659 B |
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 689 B |
After Width: | Height: | Size: 631 B |
After Width: | Height: | Size: 679 B |
|
@ -24,6 +24,17 @@
|
||||||
<file>icons/conical-flask-red.png</file>
|
<file>icons/conical-flask-red.png</file>
|
||||||
<file>icons/conical-flask-red@2x.png</file>
|
<file>icons/conical-flask-red@2x.png</file>
|
||||||
<file>icons/cover-placeholder.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.png</file>
|
||||||
<file>icons/document-open@2x.png</file>
|
<file>icons/document-open@2x.png</file>
|
||||||
<file>icons/document-save.png</file>
|
<file>icons/document-save.png</file>
|
||||||
|
|