Qt: Add custom code view for debugger

Branch arrows, syntax highlighting.
This commit is contained in:
Stenzek
2025-08-23 15:32:42 +10:00
parent e907bbf831
commit 686c4b81c1
10 changed files with 863 additions and 407 deletions

View File

@@ -57,6 +57,8 @@ set(SRCS
coverdownloadwindow.h
coverdownloadwindow.ui
debuggeraddbreakpointdialog.ui
debuggercodeview.cpp
debuggercodeview.h
debuggermodels.cpp
debuggermodels.h
debuggerwindow.cpp

View File

@@ -0,0 +1,690 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "debuggercodeview.h"
#include "core/bus.h"
#include "core/cpu_core.h"
#include "core/cpu_core_private.h"
#include "core/cpu_disasm.h"
#include "core/cpu_types.h"
#include "common/log.h"
#include "common/small_string.h"
#include <QtCore/QTimer>
#include <QtGui/QFontDatabase>
#include <QtGui/QMouseEvent>
#include <QtGui/QPainter>
#include <QtGui/QPainterPath>
#include <QtGui/QPalette>
#include <QtWidgets/QApplication>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QStyleOption>
#include <algorithm>
#include "moc_debuggercodeview.cpp"
LOG_CHANNEL(Host);
DebuggerCodeView::DebuggerCodeView(QWidget* parent) : QAbstractScrollArea(parent)
{
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
updateRowHeight();
// Load icons
m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(12);
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(12);
// Connect scroll bar
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) {
const VirtualMemoryAddress new_top = getAddressForRow(value);
// Clamp to valid range to prevent wrapping
if (new_top >= m_code_region_start && new_top < m_code_region_end)
{
m_top_address = new_top;
updateVisibleRange();
// Recalculate arrows when scroll position changes
calculateBranchArrows();
viewport()->update();
}
});
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_OpaquePaintEvent);
// Initialize code region
resetCodeView(0);
}
DebuggerCodeView::~DebuggerCodeView() = default;
void DebuggerCodeView::updateRowHeight()
{
const QFontMetrics font_metrics(fontMetrics());
m_row_height = font_metrics.height() + 2;
m_char_width = font_metrics.horizontalAdvance('M');
}
void DebuggerCodeView::resetCodeView(VirtualMemoryAddress start_address)
{
updateRegion(start_address);
calculateBranchArrows(); // Recalculate arrows when region changes
}
void DebuggerCodeView::setPC(VirtualMemoryAddress pc)
{
if (m_last_pc == pc)
return;
m_last_pc = pc;
if (!updateRegion(pc))
{
// Just update the display if region didn't change
viewport()->update();
}
else
{
// Region changed, recalculate arrows
calculateBranchArrows();
}
}
void DebuggerCodeView::ensureAddressVisible(VirtualMemoryAddress address)
{
const bool region_changed = updateRegion(address);
if (region_changed)
{
calculateBranchArrows();
}
}
void DebuggerCodeView::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);
}
else
{
auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address);
if (it == m_breakpoints.end())
return;
m_breakpoints.erase(it);
}
viewport()->update();
}
void DebuggerCodeView::clearBreakpoints()
{
m_breakpoints.clear();
viewport()->update();
}
bool DebuggerCodeView::hasBreakpointAtAddress(VirtualMemoryAddress address) const
{
return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end();
}
int DebuggerCodeView::getRowForAddress(VirtualMemoryAddress address) const
{
if (address < m_code_region_start)
return 0;
if (address >= m_code_region_end)
return static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE) - 1;
return static_cast<int>((address - m_code_region_start) / CPU::INSTRUCTION_SIZE);
}
VirtualMemoryAddress DebuggerCodeView::getAddressForRow(int row) const
{
const VirtualMemoryAddress address = m_code_region_start + (static_cast<u32>(row) * CPU::INSTRUCTION_SIZE);
return std::clamp(address, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE);
}
bool DebuggerCodeView::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(u32);
static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(u32);
const VirtualMemoryAddress start_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment);
const VirtualMemoryAddress end_address =
CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment);
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();
updateScrollBars();
viewport()->update();
return true;
}
void DebuggerCodeView::scrollToAddress(VirtualMemoryAddress address, bool center)
{
ensureAddressVisible(address);
VirtualMemoryAddress old_top = m_top_address;
if (center)
{
const int visible_rows = getVisibleRowCount();
const int target_row = getRowForAddress(address);
const int top_row = std::max(0, target_row - visible_rows / 2);
m_top_address = getAddressForRow(top_row);
}
else
{
const VirtualMemoryAddress first_visible = getFirstVisibleAddress();
const VirtualMemoryAddress last_visible = getLastVisibleAddress();
if (address < first_visible || address > last_visible)
{
m_top_address = address;
}
}
// Only recalculate arrows if scroll position actually changed
if (m_top_address != old_top)
{
calculateBranchArrows();
}
updateScrollBars();
updateVisibleRange();
viewport()->update();
}
std::optional<VirtualMemoryAddress> DebuggerCodeView::getSelectedAddress() const
{
if (!m_has_selection)
return std::nullopt;
return m_selected_address;
}
void DebuggerCodeView::setSelectedAddress(VirtualMemoryAddress address)
{
m_selected_address = address;
m_has_selection = true;
viewport()->update();
}
VirtualMemoryAddress DebuggerCodeView::getAddressAtPoint(const QPoint& point) const
{
const int top_row = getRowForAddress(m_top_address);
const int clicked_row = top_row + (point.y() / m_row_height);
return getAddressForRow(clicked_row);
}
void DebuggerCodeView::paintEvent(QPaintEvent* event)
{
QPainter painter(viewport());
painter.setFont(font());
const QRect visible_rect = event->rect();
// Calculate which rows are visible based on scroll position
const int top_row = getRowForAddress(m_top_address);
const int first_visible_row = top_row + (visible_rect.top() / m_row_height);
const int last_visible_row = top_row + ((visible_rect.bottom() + m_row_height - 1) / m_row_height);
painter.fillRect(visible_rect, palette().base());
for (int row = first_visible_row; row <= last_visible_row; ++row)
{
const VirtualMemoryAddress address = getAddressForRow(row);
// Calculate y position relative to scroll position
const int y = (row - top_row) * m_row_height;
if (y + m_row_height < visible_rect.top() || y > visible_rect.bottom())
continue;
const bool is_selected = (m_has_selection && address == m_selected_address);
const bool is_pc = (address == m_last_pc);
drawInstruction(painter, address, y, is_selected, is_pc);
}
drawBranchArrows(painter, visible_rect);
}
void DebuggerCodeView::drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected,
bool is_pc)
{
const bool has_breakpoint = hasBreakpointAtAddress(address);
// Draw background
if (has_breakpoint || is_pc || is_selected)
{
const QRect row_rect(0, y, viewport()->width(), m_row_height);
QColor bg_color;
if (has_breakpoint)
bg_color = QColor(171, 97, 107);
else if (is_pc)
bg_color = QColor(100, 100, 0);
else if (is_selected)
bg_color = palette().highlight().color();
else
bg_color = palette().base().color();
painter.fillRect(row_rect, bg_color);
}
// Set text color
QColor address_color;
QColor bytes_color;
QColor instruction_color;
QColor register_color;
QColor immediate_color;
QColor comment_color;
if (is_pc || has_breakpoint)
{
address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color = Qt::white;
}
else if (is_selected)
{
address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color =
palette().highlightedText().color();
}
else
{
instruction_color = palette().text().color();
bytes_color = bytes_color.darker(200);
address_color = instruction_color.darker(230);
register_color = QColor(0, 150, 255); // Blue for registers
immediate_color = QColor(255, 150, 0); // Orange for immediates
comment_color = QColor(150, 150, 150); // Gray for comments
}
// Start from the left edge of the arrow column
int x = ARROW_COLUMN_WIDTH + 2;
// Draw breakpoint/PC icon
if (is_pc)
{
const int icon_y = y + 2 + (m_row_height - m_pc_pixmap.height()) / 2;
painter.drawPixmap(x, icon_y, m_pc_pixmap);
}
else if (has_breakpoint)
{
const int icon_y = y + 2 + (m_row_height - m_breakpoint_pixmap.height()) / 2;
painter.drawPixmap(x, icon_y, m_breakpoint_pixmap);
}
x += BREAKPOINT_COLUMN_WIDTH;
// Draw address
const QFontMetrics font_metrics = painter.fontMetrics();
y += font_metrics.ascent() + 1;
painter.setPen(address_color);
const QString address_text = QString::asprintf("0x%08X", address);
painter.drawText(x, y, address_text);
x += ADDRESS_COLUMN_WIDTH;
// Draw instruction bytes
if (u32 instruction_bits; CPU::SafeReadInstruction(address, &instruction_bits))
{
const QString bytes_text = QString::asprintf("%08X", instruction_bits);
painter.setPen(address_color);
painter.drawText(x, y, bytes_text);
x += BYTES_COLUMN_WIDTH;
SmallString str;
CPU::DisassembleInstruction(&str, address, instruction_bits);
const QString disasm_text = QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
painter.setPen(instruction_color);
// Highlight registers and immediates in the disassembly
qsizetype start_pos = 0;
while (start_pos >= 0)
{
qsizetype end_pos = disasm_text.indexOf(' ', start_pos);
if (end_pos > 0)
end_pos++; // Include the space in the highlight
const QString token = disasm_text.mid(start_pos, end_pos - start_pos);
QColor color;
if (start_pos == 0)
color = instruction_color; // Instruction mnemonic
else if (token[0].isDigit() || (token.length() > 1 && token[0] == '-' && token[1].isDigit()))
color = immediate_color; // Immediate value
else
color = register_color; // Register
painter.setPen(color);
painter.drawText(x, y, token);
x += font_metrics.horizontalAdvance(token);
start_pos = end_pos;
}
}
else
{
painter.setPen(instruction_color);
painter.drawText(x, y, QStringLiteral("<invalid>"));
}
}
void DebuggerCodeView::calculateBranchArrows()
{
m_branch_arrows.clear();
const VirtualMemoryAddress first_visible = getFirstVisibleAddress();
const VirtualMemoryAddress last_visible = getLastVisibleAddress();
// Expand search range to include arrows that might be partially visible
const VirtualMemoryAddress search_start =
(first_visible > 64 * CPU::INSTRUCTION_SIZE) ? first_visible - (64 * CPU::INSTRUCTION_SIZE) : m_code_region_start;
const VirtualMemoryAddress search_end = std::min(last_visible + (64 * CPU::INSTRUCTION_SIZE), m_code_region_end);
for (VirtualMemoryAddress addr = search_start; addr < search_end; addr += CPU::INSTRUCTION_SIZE)
{
u32 instruction_bits;
if (!CPU::SafeReadInstruction(addr, &instruction_bits))
continue;
const CPU::Instruction instruction{instruction_bits};
if (CPU::IsDirectBranchInstruction(instruction) && !CPU::IsCallInstruction(instruction))
{
const VirtualMemoryAddress target = CPU::GetDirectBranchTarget(instruction, addr);
// Only include arrows where at least one end (source or target) is in the current region
if ((addr >= search_start && addr < search_end) && (target >= search_start && target < search_end))
{
BranchArrow arrow;
arrow.source = addr;
arrow.target = target;
arrow.is_conditional = !CPU::IsUnconditionalBranchInstruction(instruction);
arrow.is_forward = (target > addr);
m_branch_arrows.push_back(arrow);
}
}
}
}
void DebuggerCodeView::drawBranchArrows(QPainter& painter, const QRect& visible_rect)
{
if (m_branch_arrows.empty())
return;
const int top_row = getRowForAddress(m_top_address);
const int arrow_left = 2;
const int arrow_right = ARROW_COLUMN_WIDTH - 2;
const int viewport_height = viewport()->height();
static constexpr const QColor colors[] = {
QColor(0, 150, 255), QColor(255, 0, 150), QColor(0, 255, 0), QColor(255, 255, 0),
QColor(150, 0, 255), QColor(150, 255, 255), QColor(0, 255, 0), QColor(0, 150, 255),
};
for (const BranchArrow& arrow : m_branch_arrows)
{
const int source_row = getRowForAddress(arrow.source);
const int target_row = getRowForAddress(arrow.target);
// Calculate y positions relative to scroll position
const int source_y = (source_row - top_row) * m_row_height + m_row_height / 2;
const int target_y = (target_row - top_row) * m_row_height + m_row_height / 2;
// Only draw if at least part of the arrow is visible
const int min_y = std::min(source_y, target_y);
const int max_y = std::max(source_y, target_y);
// Skip if completely outside viewport
if (max_y < 0 || min_y > viewport_height)
continue;
// Clamp to viewport bounds for drawing
const int clamped_source_y = std::clamp(source_y, 0, viewport_height);
const int clamped_target_y = std::clamp(target_y, 0, viewport_height);
// Choose color based on arrow type
QColor arrow_color = colors[source_row % std::size(colors)];
painter.setPen(QPen(arrow_color, 1)); // Extra thick for debugging
int nest_level = 8 - (source_row % 8); // Adjust nesting level based on row
#if 0
for (const BranchArrow& other : m_branch_arrows)
{
if (&other == &arrow) // Skip self-comparison
break;
const int other_source_row = getRowForAddress(other.source);
const int other_target_row = getRowForAddress(other.target);
if ((other_source_row < top_row || other_source_row >= end_row) &&
(other_target_row < top_row || other_target_row >= end_row))
{
continue; // Skip arrows outside the visible range
}
const int min_row = std::min(source_row, target_row);
const int max_row = std::max(source_row, target_row);
const int other_min_row = std::min(other_source_row, other_target_row);
const int other_max_row = std::max(other_source_row, other_target_row);
// Check if ranges overlap
if (!(max_row < other_min_row || min_row > other_max_row))
{
nest_level = std::max(nest_level - 1, 0);
}
}
#endif
const int arrow_x = arrow_left + (nest_level * 4);
// Draw debug circles at source and target positions
painter.setBrush(arrow_color);
painter.drawEllipse(arrow_right - 1, source_y - 1, 3, 3);
// Draw straight line arrow with right angles
if (source_y == target_y)
{
// Horizontal line for same-row branches
painter.drawLine(arrow_x, source_y, arrow_x + 15, target_y);
}
else
{
// Horizontal line from left edge to source
if (source_y >= 0 && source_y < viewport_height)
painter.drawLine(arrow_x, source_y, arrow_right, source_y);
// Vertical line from source to target
painter.drawLine(arrow_x, clamped_source_y, arrow_x, clamped_target_y);
// Horizontal line to target
if (target_y >= 0 && target_y < viewport_height)
painter.drawLine(arrow_x, target_y, arrow_right, target_y);
// Draw arrowhead with simple lines
const int arrow_size = 3;
const bool pointing_down = (target_y > source_y);
if (pointing_down)
{
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size);
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size);
}
else
{
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size);
painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size);
}
}
}
}
void DebuggerCodeView::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
setSelectedAddress(address);
}
QAbstractScrollArea::mousePressEvent(event);
}
void DebuggerCodeView::mouseMoveEvent(QMouseEvent* event)
{
if (event->buttons() & Qt::LeftButton)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
setSelectedAddress(address);
}
QAbstractScrollArea::mouseMoveEvent(event);
}
void DebuggerCodeView::mouseDoubleClickEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
const int x = event->pos().x();
if ((x >= BREAKPOINT_COLUMN_START && x < ADDRESS_COLUMN_START) ||
(x >= INSTRUCTION_COLUMN_START && x < COMMENT_COLUMN_START))
{
emit toggleBreakpointActivated(address);
}
else if (x >= ADDRESS_COLUMN_START && x < INSTRUCTION_COLUMN_START)
{
emit addressActivated(address);
}
else
{
emit commentActivated(address);
}
}
QAbstractScrollArea::mouseDoubleClickEvent(event);
}
void DebuggerCodeView::contextMenuEvent(QContextMenuEvent* event)
{
const VirtualMemoryAddress address = getAddressAtPoint(event->pos());
emit contextMenuRequested(event->pos(), address);
}
void DebuggerCodeView::keyPressEvent(QKeyEvent* event)
{
if (!m_has_selection)
{
QAbstractScrollArea::keyPressEvent(event);
return;
}
VirtualMemoryAddress new_address = m_selected_address;
switch (event->key())
{
case Qt::Key_Up:
new_address -= CPU::INSTRUCTION_SIZE;
break;
case Qt::Key_Down:
new_address += CPU::INSTRUCTION_SIZE;
break;
case Qt::Key_PageUp:
new_address -= CPU::INSTRUCTION_SIZE * getVisibleRowCount();
break;
case Qt::Key_PageDown:
new_address += CPU::INSTRUCTION_SIZE * getVisibleRowCount();
break;
default:
QAbstractScrollArea::keyPressEvent(event);
return;
}
scrollToAddress(new_address, false);
setSelectedAddress(new_address);
}
void DebuggerCodeView::wheelEvent(QWheelEvent* event)
{
const int delta = event->angleDelta().y();
const int steps = delta / 120; // Standard wheel step
VirtualMemoryAddress old_top = m_top_address;
VirtualMemoryAddress new_top = m_top_address - (steps * CPU::INSTRUCTION_SIZE * 3);
// Clamp to valid range
new_top = std::clamp(new_top, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE);
m_top_address = new_top;
// Only recalculate arrows if scroll position actually changed
if (m_top_address != old_top)
{
calculateBranchArrows();
}
updateScrollBars();
updateVisibleRange();
viewport()->update();
event->accept();
}
void DebuggerCodeView::resizeEvent(QResizeEvent* event)
{
QAbstractScrollArea::resizeEvent(event);
updateScrollBars();
updateVisibleRange();
// Recalculate arrows on resize since visible range changes
calculateBranchArrows();
}
void DebuggerCodeView::updateScrollBars()
{
const int total_rows = static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE);
const int visible_rows = getVisibleRowCount();
const int max_value = std::max(0, total_rows - visible_rows);
verticalScrollBar()->setRange(0, max_value);
verticalScrollBar()->setPageStep(visible_rows);
const int current_row = getRowForAddress(m_top_address);
verticalScrollBar()->setValue(current_row);
}
void DebuggerCodeView::updateVisibleRange()
{
m_visible_rows = getVisibleRowCount();
}
int DebuggerCodeView::getVisibleRowCount() const
{
return viewport()->height() / m_row_height;
}
VirtualMemoryAddress DebuggerCodeView::getFirstVisibleAddress() const
{
return m_top_address;
}
VirtualMemoryAddress DebuggerCodeView::getLastVisibleAddress() const
{
const int visible_rows = getVisibleRowCount();
return m_top_address + (visible_rows * CPU::INSTRUCTION_SIZE);
}

View File

@@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "core/bus.h"
#include "core/cpu_types.h"
#include "core/types.h"
#include <QtCore/QPoint>
#include <QtGui/QPixmap>
#include <QtWidgets/QWidget>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QAbstractScrollArea>
#include <memory>
#include <optional>
#include <vector>
class DebuggerCodeView : public QAbstractScrollArea
{
Q_OBJECT
public:
explicit DebuggerCodeView(QWidget* parent = nullptr);
~DebuggerCodeView();
// Call when font or theme changes
void updateRowHeight();
void scrollToAddress(VirtualMemoryAddress address, bool center = false);
std::optional<VirtualMemoryAddress> getSelectedAddress() const;
void setSelectedAddress(VirtualMemoryAddress address);
VirtualMemoryAddress getAddressAtPoint(const QPoint& point) const;
// Code model functionality integrated
void resetCodeView(VirtualMemoryAddress start_address);
void setPC(VirtualMemoryAddress pc);
void ensureAddressVisible(VirtualMemoryAddress address);
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
void clearBreakpoints();
VirtualMemoryAddress getPC() const { return m_last_pc; }
bool hasBreakpointAtAddress(VirtualMemoryAddress address) const;
Q_SIGNALS:
void toggleBreakpointActivated(VirtualMemoryAddress address);
void addressActivated(VirtualMemoryAddress address);
void commentActivated(VirtualMemoryAddress address);
void contextMenuRequested(const QPoint& point, VirtualMemoryAddress address);
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void contextMenuEvent(QContextMenuEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
private:
// Column positions
static constexpr int ARROW_COLUMN_WIDTH = 60;
static constexpr int BREAKPOINT_COLUMN_WIDTH = 20;
static constexpr int ADDRESS_COLUMN_WIDTH = 100;
static constexpr int BYTES_COLUMN_WIDTH = 90;
static constexpr int INSTRUCTION_COLUMN_WIDTH = 250;
static constexpr int BREAKPOINT_COLUMN_START = ARROW_COLUMN_WIDTH;
static constexpr int ADDRESS_COLUMN_START = BREAKPOINT_COLUMN_START + BREAKPOINT_COLUMN_WIDTH;
static constexpr int BYTES_COLUMN_START = ADDRESS_COLUMN_START + ADDRESS_COLUMN_WIDTH;
static constexpr int INSTRUCTION_COLUMN_START = BYTES_COLUMN_START + BYTES_COLUMN_WIDTH;
static constexpr int COMMENT_COLUMN_START = INSTRUCTION_COLUMN_START + INSTRUCTION_COLUMN_WIDTH;
struct BranchArrow
{
VirtualMemoryAddress source;
VirtualMemoryAddress target;
bool is_conditional;
bool is_forward;
};
// Address/row conversion methods
int getRowForAddress(VirtualMemoryAddress address) const;
VirtualMemoryAddress getAddressForRow(int row) const;
// Memory region management
bool updateRegion(VirtualMemoryAddress address);
// Drawing methods
void updateScrollBars();
void updateVisibleRange();
void calculateBranchArrows();
void drawBranchArrows(QPainter& painter, const QRect& visible_rect);
void drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected, bool is_pc);
int getVisibleRowCount() const;
VirtualMemoryAddress getFirstVisibleAddress() const;
VirtualMemoryAddress getLastVisibleAddress() const;
int m_row_height = 1;
int m_char_width = 0;
VirtualMemoryAddress m_selected_address = 0;
bool m_has_selection = false;
std::vector<BranchArrow> m_branch_arrows;
QPixmap m_pc_pixmap;
QPixmap m_breakpoint_pixmap;
// Scroll state
VirtualMemoryAddress m_top_address = 0;
int m_visible_rows = 0;
// Code region state (from DebuggerCodeModel)
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;
};

View File

@@ -18,285 +18,9 @@
#include "moc_debuggermodels.cpp"
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(12);
m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(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 tr("<invalid>");
return QString::asprintf("%08X", instruction_bits);
}
case 3:
{
// Instruction
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return tr("<invalid>");
SmallString str;
CPU::DisassembleInstruction(&str, address, instruction_bits);
return QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
}
case 4:
{
// Comment
if (address != m_last_pc)
return QVariant();
u32 instruction_bits;
if (!CPU::SafeReadInstruction(address, &instruction_bits))
return tr("<invalid>");
TinyString str;
CPU::DisassembleInstructionComment(&str, address, instruction_bits);
return QString::fromUtf8(str.c_str(), static_cast<int>(str.length()));
}
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();
switch (section)
{
case 1:
return tr("Address");
case 2:
return tr("Bytes");
case 3:
return tr("Instruction");
case 4:
return tr("Comment");
case 0:
default:
return QVariant();
}
}
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)
{
}

View File

@@ -15,47 +15,6 @@
#include <QtWidgets/QDialog>
#include <map>
class DebuggerCodeModel final : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DebuggerCodeModel(QObject* parent = nullptr);
~DebuggerCodeModel() override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
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 final : public QAbstractListModel
{
Q_OBJECT

View File

@@ -83,7 +83,7 @@ void DebuggerWindow::refreshAll()
m_stack_model->invalidateView();
m_ui.memoryView->forceRefresh();
m_code_model->setPC(CPU::g_state.pc);
m_ui.codeView->setPC(CPU::g_state.pc);
scrollToPC(false);
}
@@ -94,25 +94,8 @@ void DebuggerWindow::scrollToPC(bool center)
void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address, bool center)
{
m_code_model->ensureAddressVisible(address);
const int row = m_code_model->getRowForAddress(address);
if (row >= 0)
{
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
const QModelIndex index = m_code_model->index(row, 0);
const QRect rect = m_ui.codeView->visualRect(index);
if (rect.left() < 0 || rect.top() < 0 || rect.right() > m_ui.codeView->viewport()->width() ||
rect.bottom() > m_ui.codeView->viewport()->height())
{
center = true;
}
m_ui.codeView->scrollTo(index, center ? QAbstractItemView::PositionAtCenter : QAbstractItemView::EnsureVisible);
m_ui.codeView->selectionModel()->setCurrentIndex(index,
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
m_ui.codeView->scrollToAddress(address, center);
m_ui.codeView->setSelectedAddress(address);
}
void DebuggerWindow::onPauseActionToggled(bool paused)
@@ -128,7 +111,7 @@ void DebuggerWindow::onPauseActionToggled(bool paused)
void DebuggerWindow::onRunToCursorTriggered()
{
std::optional<VirtualMemoryAddress> addr = getSelectedCodeAddress();
std::optional<VirtualMemoryAddress> addr = m_ui.codeView->getSelectedAddress();
if (!addr.has_value())
{
QMessageBox::critical(this, windowTitle(), tr("No address selected."));
@@ -180,11 +163,6 @@ void DebuggerWindow::onTraceTriggered()
}
}
void DebuggerWindow::onFollowAddressTriggered()
{
//
}
void DebuggerWindow::onAddBreakpointTriggered()
{
DebuggerAddBreakpointDialog dlg(this);
@@ -196,7 +174,7 @@ void DebuggerWindow::onAddBreakpointTriggered()
void DebuggerWindow::onToggleBreakpointTriggered()
{
std::optional<VirtualMemoryAddress> address = getSelectedCodeAddress();
std::optional<VirtualMemoryAddress> address = m_ui.codeView->getSelectedAddress();
if (!address.has_value())
return;
@@ -281,37 +259,26 @@ void DebuggerWindow::onStepOutActionTriggered()
g_emu_thread->setSystemPaused(false);
}
void DebuggerWindow::onCodeViewItemActivated(QModelIndex index)
void DebuggerWindow::onCodeViewAddressActivated(VirtualMemoryAddress address)
{
if (!index.isValid())
return;
scrollToMemoryAddress(address);
}
const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index);
switch (index.column())
{
case 0: // breakpoint
case 3: // disassembly
toggleBreakpoint(address);
break;
void DebuggerWindow::onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address)
{
toggleBreakpoint(address);
}
case 1: // address
case 2: // bytes
scrollToMemoryAddress(address);
break;
case 4: // comment
tryFollowLoadStore(address);
break;
}
void DebuggerWindow::onCodeViewCommentActivated(VirtualMemoryAddress address)
{
if (!tryFollowLoadStore(address))
toggleBreakpoint(address);
}
void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt)
{
const QModelIndex index = m_ui.codeView->indexAt(pt);
if (!index.isValid())
return;
const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index);
const VirtualMemoryAddress address = m_ui.codeView->getAddressAtPoint(pt);
m_ui.codeView->setSelectedAddress(address);
QMenu menu;
menu.addAction(QStringLiteral("0x%1").arg(static_cast<uint>(address), 8, 16, QChar('0')))->setEnabled(false);
@@ -484,6 +451,7 @@ void DebuggerWindow::setupAdditionalUi()
#endif
#endif
m_ui.codeView->setFont(fixedFont);
m_ui.codeView->updateRowHeight();
m_ui.registerView->setFont(fixedFont);
m_ui.memoryView->setFont(fixedFont);
m_ui.stackView->setFont(fixedFont);
@@ -518,8 +486,11 @@ void DebuggerWindow::connectSignals()
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.codeView, &QTreeView::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested);
connect(m_ui.codeView, &DebuggerCodeView::addressActivated, this, &DebuggerWindow::onCodeViewAddressActivated);
connect(m_ui.codeView, &DebuggerCodeView::toggleBreakpointActivated, this,
&DebuggerWindow::onCodeViewToggleBreakpointActivated);
connect(m_ui.codeView, &DebuggerCodeView::commentActivated, this, &DebuggerWindow::onCodeViewCommentActivated);
connect(m_ui.codeView, &QWidget::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested);
connect(m_ui.breakpointsWidget, &QTreeWidget::customContextMenuRequested, this,
&DebuggerWindow::onBreakpointListContextMenuRequested);
connect(m_ui.breakpointsWidget, &QTreeWidget::itemChanged, this, &DebuggerWindow::onBreakpointListItemChanged);
@@ -545,22 +516,12 @@ void DebuggerWindow::disconnectSignals()
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, 100);
m_ui.codeView->setColumnWidth(2, 90);
m_ui.codeView->setColumnWidth(3, 250);
m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 100 + 90 + 250));
m_registers_model = std::make_unique<DebuggerRegistersModel>();
m_ui.registerView->setModel(m_registers_model.get());
m_registers_model = new DebuggerRegistersModel(this);
m_ui.registerView->setModel(m_registers_model);
// m_ui->registerView->resizeRowsToContents();
m_stack_model = std::make_unique<DebuggerStackModel>();
m_ui.stackView->setModel(m_stack_model.get());
m_stack_model = new DebuggerStackModel(this);
m_ui.stackView->setModel(m_stack_model);
m_ui.breakpointsWidget->setColumnWidth(0, 50);
m_ui.breakpointsWidget->setColumnWidth(1, 80);
@@ -670,7 +631,7 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address)
}
Host::RunOnUIThread([this, address, new_bp_state, bps = CPU::CopyBreakpointList()]() {
m_code_model->setBreakpointState(address, new_bp_state);
m_ui.codeView->setBreakpointState(address, new_bp_state);
refreshBreakpointList(bps);
});
});
@@ -678,20 +639,10 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address)
void DebuggerWindow::clearBreakpoints()
{
m_code_model->clearBreakpoints();
m_ui.codeView->clearBreakpoints();
Host::RunOnCPUThread(&CPU::ClearBreakpoints);
}
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;
@@ -760,7 +711,7 @@ void DebuggerWindow::addBreakpoint(CPU::BreakpointType type, u32 address)
}
if (type == CPU::BreakpointType::Execute)
m_code_model->setBreakpointState(address, true);
m_ui.codeView->setBreakpointState(address, true);
refreshBreakpointList(bps);
});
@@ -779,7 +730,7 @@ void DebuggerWindow::removeBreakpoint(CPU::BreakpointType type, u32 address)
}
if (type == CPU::BreakpointType::Execute)
m_code_model->setBreakpointState(address, false);
m_ui.codeView->setBreakpointState(address, false);
refreshBreakpointList(bps);
});

View File

@@ -10,14 +10,12 @@
#include <QtCore/QTimer>
#include <QtWidgets/QMainWindow>
#include <memory>
#include <optional>
namespace Bus {
enum class MemoryRegion;
}
class DebuggerCodeModel;
class DebuggerRegistersModel;
class DebuggerStackModel;
@@ -50,7 +48,6 @@ private Q_SLOTS:
void onGoToPCTriggered();
void onGoToAddressTriggered();
void onDumpAddressTriggered();
void onFollowAddressTriggered();
void onTraceTriggered();
void onAddBreakpointTriggered();
void onToggleBreakpointTriggered();
@@ -60,7 +57,9 @@ private Q_SLOTS:
void onStepIntoActionTriggered();
void onStepOverActionTriggered();
void onStepOutActionTriggered();
void onCodeViewItemActivated(QModelIndex index);
void onCodeViewAddressActivated(VirtualMemoryAddress address);
void onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address);
void onCodeViewCommentActivated(VirtualMemoryAddress address);
void onCodeViewContextMenuRequested(const QPoint& pt);
void onMemorySearchTriggered();
void onMemorySearchStringChanged(const QString&);
@@ -75,7 +74,6 @@ private:
void setMemoryViewRegion(Bus::MemoryRegion region);
void toggleBreakpoint(VirtualMemoryAddress address);
void clearBreakpoints();
std::optional<VirtualMemoryAddress> getSelectedCodeAddress();
bool tryFollowLoadStore(VirtualMemoryAddress address);
void scrollToPC(bool center);
void scrollToCodeAddress(VirtualMemoryAddress address, bool center);
@@ -87,9 +85,8 @@ private:
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;
DebuggerRegistersModel* m_registers_model;
DebuggerStackModel* m_stack_model;
QTimer m_refresh_timer;

View File

@@ -94,7 +94,7 @@
<attribute name="dockWidgetArea">
<number>4</number>
</attribute>
<widget class="QTreeView" name="codeView">
<widget class="DebuggerCodeView" name="codeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@@ -481,6 +481,12 @@
<header>duckstation-qt/memoryviewwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>DebuggerCodeView</class>
<extends>QAbstractScrollArea</extends>
<header>duckstation-qt/debuggercodeview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="resources/resources.qrc"/>

View File

@@ -15,6 +15,7 @@
<ClCompile Include="controllerglobalsettingswidget.cpp" />
<ClCompile Include="controllersettingswindow.cpp" />
<ClCompile Include="coverdownloadwindow.cpp" />
<ClCompile Include="debuggercodeview.cpp" />
<ClCompile Include="emulationsettingswidget.cpp" />
<ClCompile Include="debuggermodels.cpp" />
<ClCompile Include="debuggerwindow.cpp" />
@@ -84,6 +85,7 @@
<QtMoc Include="gamecheatsettingswidget.h" />
<QtMoc Include="gamepatchsettingswidget.h" />
<QtMoc Include="isobrowserwindow.h" />
<QtMoc Include="debuggercodeview.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="resource.h" />
<QtMoc Include="selectdiscdialog.h" />

View File

@@ -48,6 +48,7 @@
<ClCompile Include="gamecheatsettingswidget.cpp" />
<ClCompile Include="gamepatchsettingswidget.cpp" />
<ClCompile Include="isobrowserwindow.cpp" />
<ClCompile Include="debuggercodeview.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="qtutils.h" />
@@ -107,6 +108,7 @@
<QtMoc Include="gamecheatsettingswidget.h" />
<QtMoc Include="gamepatchsettingswidget.h" />
<QtMoc Include="isobrowserwindow.h" />
<QtMoc Include="debuggercodeview.h" />
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui" />