Merge branch 'master' of https://github.com/dolphin-emu/dolphin into dolphin-emu-master

This commit is contained in:
Nayla Hanegan 2024-03-03 19:37:55 -05:00
commit f873d6ff71
No known key found for this signature in database
GPG key ID: 3075216CED0DB01D
239 changed files with 18152 additions and 20748 deletions

View file

@ -150,10 +150,7 @@ void AchievementHeaderWidget::UpdateData()
m_game_progress_hard->setVisible(false);
m_game_progress_soft->setVisible(false);
m_rich_presence->setVisible(false);
if (instance.IsDisabled())
{
m_locked_warning->setVisible(true);
}
m_locked_warning->setVisible(instance.IsDisabled());
}
}

View file

@ -206,12 +206,14 @@ add_executable(dolphin-mpn
Debugger/AssemblerWidget.h
Debugger/AssemblyEditor.cpp
Debugger/AssemblyEditor.h
Debugger/BranchWatchDialog.cpp
Debugger/BranchWatchDialog.h
Debugger/BranchWatchTableModel.cpp
Debugger/BranchWatchTableModel.h
Debugger/BreakpointDialog.cpp
Debugger/BreakpointDialog.h
Debugger/BreakpointWidget.cpp
Debugger/BreakpointWidget.h
Debugger/CodeDiffDialog.cpp
Debugger/CodeDiffDialog.h
Debugger/CodeViewWidget.cpp
Debugger/CodeViewWidget.h
Debugger/CodeWidget.cpp

View file

@ -19,7 +19,6 @@
#include "Common/StringUtil.h"
#include "Core/CheatSearch.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/MMU.h"
@ -170,7 +169,7 @@ void CheatSearchFactoryWidget::OnNewSearchClicked()
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
memory_ranges.emplace_back(0x80000000, memory.GetRamSizeReal());
if (SConfig::GetInstance().bWii)
if (system.IsWii())
memory_ranges.emplace_back(0x90000000, memory.GetExRamSizeReal());
address_space = PowerPC::RequestedAddressSpace::Virtual;
}

View file

@ -301,9 +301,7 @@ void CheatSearchWidget::OnNextScanClicked()
}
}
const Cheats::SearchErrorCode error_code = [this, &guard] {
return m_session->RunSearch(guard);
}();
const Cheats::SearchErrorCode error_code = m_session->RunSearch(guard);
if (error_code == Cheats::SearchErrorCode::Success)
{

View file

@ -55,9 +55,9 @@ void GeneralWidget::CreateWidgets()
m_video_layout = new QGridLayout();
m_backend_combo = new ToolTipComboBox();
m_aspect_combo = new ConfigChoice(
{tr("Auto"), tr("Force 16:9"), tr("Force 4:3"), tr("Stretch to Window"), tr("Custom")},
Config::GFX_ASPECT_RATIO);
m_aspect_combo = new ConfigChoice({tr("Auto"), tr("Force 16:9"), tr("Force 4:3"),
tr("Stretch to Window"), tr("Custom"), tr("Custom (Stretch)")},
Config::GFX_ASPECT_RATIO);
m_custom_aspect_label = new QLabel(tr("Custom Aspect Ratio:"));
m_custom_aspect_label->setHidden(true);
constexpr int MAX_CUSTOM_ASPECT_RATIO_RESOLUTION = 10000;
@ -157,7 +157,8 @@ void GeneralWidget::ConnectWidgets()
emit BackendChanged(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)));
});
connect(m_aspect_combo, qOverload<int>(&QComboBox::currentIndexChanged), this, [&](int index) {
const bool is_custom_aspect_ratio = (index == static_cast<int>(AspectMode::Custom));
const bool is_custom_aspect_ratio = (index == static_cast<int>(AspectMode::Custom)) ||
(index == static_cast<int>(AspectMode::CustomStretch));
m_custom_aspect_width->setEnabled(is_custom_aspect_ratio);
m_custom_aspect_height->setEnabled(is_custom_aspect_ratio);
m_custom_aspect_label->setHidden(!is_custom_aspect_ratio);
@ -172,7 +173,9 @@ void GeneralWidget::LoadSettings()
m_backend_combo->setCurrentIndex(m_backend_combo->findData(
QVariant(QString::fromStdString(Config::Get(Config::MAIN_GFX_BACKEND)))));
const bool is_custom_aspect_ratio = (Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::Custom);
const bool is_custom_aspect_ratio =
(Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::Custom) ||
(Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::CustomStretch);
m_custom_aspect_width->setEnabled(is_custom_aspect_ratio);
m_custom_aspect_height->setEnabled(is_custom_aspect_ratio);
m_custom_aspect_label->setHidden(!is_custom_aspect_ratio);
@ -249,15 +252,19 @@ void GeneralWidget::AddDescriptions()
QT_TR_NOOP("Uses the main Dolphin window for rendering rather than "
"a separate render window.<br><br><dolphin_emphasis>If unsure, leave "
"this unchecked.</dolphin_emphasis>");
static const char TR_ASPECT_RATIO_DESCRIPTION[] =
QT_TR_NOOP("Selects which aspect ratio to use when rendering.<br>"
"Each game can have a slightly different native aspect ratio."
"<br><br>Auto: Uses the native aspect ratio"
"<br>Force 16:9: Mimics an analog TV with a widescreen aspect ratio."
"<br>Force 4:3: Mimics a standard 4:3 analog TV."
"<br>Stretch to Window: Stretches the picture to the window size."
"<br>Custom: For games running with specific custom aspect ratio cheats.<br><br>"
"<dolphin_emphasis>If unsure, select Auto.</dolphin_emphasis>");
static const char TR_ASPECT_RATIO_DESCRIPTION[] = QT_TR_NOOP(
"Selects which aspect ratio to use when drawing on the render window.<br>"
"Each game can have a slightly different native aspect ratio.<br>They can vary by "
"scene and settings and rarely ever exactly match 4:3 or 16:9."
"<br><br><b>Auto</b>: Uses the native aspect ratio"
"<br><br><b>Force 16:9</b>: Mimics an analog TV with a widescreen aspect ratio."
"<br><br><b>Force 4:3</b>: Mimics a standard 4:3 analog TV."
"<br><br><b>Stretch to Window</b>: Stretches the picture to the window size."
"<br><br><b>Custom</b>: Forces the specified aspect ratio."
"<br>This is mostly intended to be used with aspect ratio cheats/mods."
"<br><br><b>Custom (Stretch)</b>: Similar to `Custom` but not relative to the "
"title's native aspect ratio.<br>This is not meant to be used under normal circumstances."
"<br><br><dolphin_emphasis>If unsure, select Auto.</dolphin_emphasis>");
static const char TR_VSYNC_DESCRIPTION[] = QT_TR_NOOP(
"Waits for vertical blanks in order to prevent tearing.<br><br>Decreases performance "
"if emulation speed is below 100%.<br><br><dolphin_emphasis>If unsure, leave "

View file

@ -185,7 +185,7 @@ void GraphicsModListWidget::ModItemChanged(QListWidgetItem* item)
m_needs_save = true;
}
void GraphicsModListWidget::OnModChanged(std::optional<std::string> absolute_path)
void GraphicsModListWidget::OnModChanged(const std::optional<std::string>& absolute_path)
{
ClearLayoutRecursively(m_mod_meta_layout);
@ -198,7 +198,7 @@ void GraphicsModListWidget::OnModChanged(std::optional<std::string> absolute_pat
return;
}
GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path);
const GraphicsModConfig* mod = m_mod_group.GetMod(*absolute_path);
if (!mod)
return;

View file

@ -52,7 +52,7 @@ private:
void ModSelectionChanged();
void ModItemChanged(QListWidgetItem* item);
void OnModChanged(std::optional<std::string> absolute_path);
void OnModChanged(const std::optional<std::string>& absolute_path);
void SaveModList();

View file

@ -319,9 +319,8 @@ void MappingWindow::OnSaveProfilePressed()
if (profile_name.isEmpty())
return;
const std::string profile_path = File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR +
m_config->GetProfileName() + "/" + profile_name.toStdString() +
".ini";
const std::string profile_path =
m_config->GetUserProfileDirectoryPath() + profile_name.toStdString() + ".ini";
File::CreateFullPath(profile_path);
@ -487,8 +486,7 @@ void MappingWindow::PopulateProfileSelection()
{
m_profiles_combo->clear();
const std::string profiles_path =
File::GetUserPath(D_CONFIG_IDX) + PROFILES_DIR + m_config->GetProfileName();
const std::string profiles_path = m_config->GetUserProfileDirectoryPath();
for (const auto& filename : Common::DoFileSearch({profiles_path}, {".ini"}))
{
std::string basename;
@ -499,9 +497,8 @@ void MappingWindow::PopulateProfileSelection()
m_profiles_combo->insertSeparator(m_profiles_combo->count());
const std::string builtin_profiles_path =
File::GetSysDirectory() + PROFILES_DIR + m_config->GetProfileName();
for (const auto& filename : Common::DoFileSearch({builtin_profiles_path}, {".ini"}))
for (const auto& filename :
Common::DoFileSearch({m_config->GetSysProfileDirectoryPath()}, {".ini"}))
{
std::string basename;
SplitPath(filename, nullptr, &basename, nullptr);

View file

@ -6,24 +6,25 @@
#include <memory>
#include <QBitmap>
#include <QGraphicsEffect>
#include <QGraphicsView>
#include <QGridLayout>
#include <QBrush>
#include <QCursor>
#include <QFont>
#include <QGuiApplication>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QPushButton>
#include <QStyle>
#include <QPen>
#include <QPoint>
#include <QRect>
#include <QScreen>
#include <QSize>
#include <QString>
#include <QVBoxLayout>
#if defined(__APPLE__)
#include <QToolTip>
#endif
#include "Core/Config/MainSettings.h"
#include "DolphinQt/Settings.h"
namespace
@ -31,8 +32,9 @@ namespace
std::unique_ptr<BalloonTip> s_the_balloon_tip = nullptr;
} // namespace
void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QString& message,
const QPoint& pos, QWidget* parent, ShowArrow show_arrow)
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
const QPoint& target_arrow_tip_position, QWidget* const parent,
const ShowArrow show_arrow, const int border_width)
{
HideBalloon();
if (message.isEmpty() && title.isEmpty())
@ -42,10 +44,10 @@ void BalloonTip::ShowBalloon(const QIcon& icon, const QString& title, const QStr
QString the_message = message;
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
QToolTip::showText(pos, the_message, parent);
QToolTip::showText(target_arrow_tip_position, the_message, parent);
#else
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, icon, title, message, parent);
s_the_balloon_tip->UpdateBoundsAndRedraw(pos, show_arrow);
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent);
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, show_arrow, border_width);
#endif
}
@ -54,20 +56,16 @@ void BalloonTip::HideBalloon()
#if defined(__APPLE__)
QToolTip::hideText();
#else
if (!s_the_balloon_tip)
if (s_the_balloon_tip == nullptr)
return;
s_the_balloon_tip->hide();
s_the_balloon_tip.reset();
#endif
}
BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString message,
QWidget* parent)
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
: QWidget(nullptr, Qt::ToolTip)
{
setAttribute(Qt::WA_DeleteOnClose);
setAutoFillBackground(true);
QColor window_color;
QColor text_color;
QColor dolphin_emphasis;
@ -78,43 +76,41 @@ BalloonTip::BalloonTip(PrivateTag, const QIcon& icon, QString title, QString mes
.arg(text_color.rgba(), 0, 16);
setStyleSheet(style_sheet);
// Replace text in our our message
// if specific "tags" are used
// Replace text in our our message if specific "tags" are used
message.replace(QStringLiteral("<dolphin_emphasis>"),
QStringLiteral("<font color=\"#%1\"><b>").arg(dolphin_emphasis.rgba(), 0, 16));
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
auto* title_label = new QLabel;
title_label->installEventFilter(this);
title_label->setText(title);
QFont f = title_label->font();
f.setBold(true);
title_label->setFont(f);
title_label->setTextFormat(Qt::RichText);
title_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
QSizePolicy::Policy::MinimumExpanding);
auto* const balloontip_layout = new QVBoxLayout;
balloontip_layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(balloontip_layout);
auto* message_label = new QLabel;
message_label->installEventFilter(this);
message_label->setText(message);
message_label->setTextFormat(Qt::RichText);
message_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
const auto create_label = [=](const QString& text) {
QLabel* const label = new QLabel;
balloontip_layout->addWidget(label);
const int limit = message_label->screen()->availableGeometry().width() / 3;
message_label->setMaximumWidth(limit);
message_label->setSizePolicy(QSizePolicy::Policy::MinimumExpanding,
QSizePolicy::Policy::MinimumExpanding);
if (message_label->sizeHint().width() > limit)
label->setText(text);
label->setTextFormat(Qt::RichText);
const int max_width = label->screen()->availableGeometry().width() / 3;
label->setMaximumWidth(max_width);
if (label->sizeHint().width() > max_width)
label->setWordWrap(true);
return label;
};
if (!title.isEmpty())
{
message_label->setWordWrap(true);
QLabel* const title_label = create_label(title);
QFont title_font = title_label->font();
title_font.setBold(true);
title_label->setFont(title_font);
}
auto* layout = new QGridLayout;
layout->addWidget(title_label, 0, 0, 1, 2);
layout->addWidget(message_label, 1, 0, 1, 3);
layout->setSizeConstraint(QLayout::SetMinimumSize);
setLayout(layout);
if (!message.isEmpty())
create_label(message);
}
void BalloonTip::paintEvent(QPaintEvent*)
@ -123,116 +119,215 @@ void BalloonTip::paintEvent(QPaintEvent*)
painter.drawPixmap(rect(), m_pixmap);
}
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& pos, ShowArrow show_arrow)
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
const ShowArrow show_arrow, const int border_full_width)
{
m_show_arrow = show_arrow == ShowArrow::Yes;
const float border_half_width = border_full_width / 2.0;
QScreen* screen = QGuiApplication::screenAt(pos);
if (!screen)
screen = QGuiApplication::primaryScreen();
// This should be odd so that the arrow tip is a single pixel wide.
const int arrow_full_width = 35;
const float arrow_half_width = arrow_full_width / 2.0;
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
// border, and also the distance between the outer edge of the rectangle border and the outer tip
// of the arrow border
const int arrow_height = (1 + arrow_full_width) / 2;
// Distance between the label layout and the inner rectangle border edge
const int balloon_interior_padding = 12;
// Prevent the corners of the label layout from portruding into the rounded rectangle corners at
// larger border sizes.
const int rounded_corner_margin = border_half_width / 4;
const int horizontal_margin =
border_full_width + rounded_corner_margin + balloon_interior_padding;
const int vertical_margin = horizontal_margin + arrow_height;
// Create enough space around the layout containing the title and message to draw the balloon and
// both arrow tips (at most one of which will be visible)
layout()->setContentsMargins(horizontal_margin, vertical_margin, horizontal_margin,
vertical_margin);
QSize size_hint = sizeHint();
// These positions represent the middle of each edge of the BalloonTip's rounded rectangle
const float rect_width = size_hint.width() - border_full_width;
const float rect_height = size_hint.height() - border_full_width - 2 * arrow_height;
const float rect_top = border_half_width + arrow_height;
const float rect_bottom = rect_top + rect_height;
const float rect_left = border_half_width;
// rect_right isn't used for anything
// Qt defines the radius of a rounded rectangle as "the radius of the ellipses defining the
// corner". Unlike the rectangle's edges this corresponds to the outside of the rounded curve
// instead of its middle, so we add the full width to the inner radius instead of the half width
const float corner_base_inner_radius = 7.0;
const float corner_outer_radius = corner_base_inner_radius + border_full_width;
// This value is arbitrary but works well.
const int base_arrow_x_offset = 34;
// Adjust the offset inward to compensate for the border and rounded corner widths. This ensures
// the arrow is on the flat part of the top/bottom border.
const int adjusted_arrow_x_offset =
base_arrow_x_offset + border_full_width + static_cast<int>(border_half_width);
// If the border is wide enough (or the BalloonTip small enough) the offset might end up past the
// midpoint; if that happens just use the midpoint instead
const int centered_arrow_x_offset = (size_hint.width() - arrow_full_width) / 2;
// If the arrow is on the left this is the distance between the left edge of the BalloonTip and
// the left edge of the arrow interior; otherwise the distance between the right edges.
const int arrow_nearest_edge_x_offset =
std::min(adjusted_arrow_x_offset, centered_arrow_x_offset);
const int arrow_tip_x_offset = arrow_nearest_edge_x_offset + arrow_half_width;
// The BalloonTip should be contained entirely within the screen that contains the target
// position.
QScreen* screen = QGuiApplication::screenAt(target_arrow_tip_position);
if (screen == nullptr)
{
// If the target position isn't on any screen (which can happen if the window is partly off the
// screen and the user hovers over the label) then use the screen containing the cursor instead.
screen = QGuiApplication::screenAt(QCursor::pos());
}
const QRect screen_rect = screen->geometry();
QSize sh = sizeHint();
// The look should resemble the default tooltip style set in Settings::ApplyStyle()
const int border = 1;
const int arrow_height = 18;
const int arrow_width = 18;
const int arrow_offset = 52;
const int rect_center = 7;
const bool arrow_at_bottom = (pos.y() - sh.height() - arrow_height > 0);
const bool arrow_at_left = (pos.x() + sh.width() - arrow_width < screen_rect.width());
const int default_padding = 10;
layout()->setContentsMargins(border + 3 + default_padding,
border + (arrow_at_bottom ? 0 : arrow_height) + 2 + default_padding,
border + 3 + default_padding,
border + (arrow_at_bottom ? arrow_height : 0) + 2 + default_padding);
updateGeometry();
sh = sizeHint();
QPainterPath rect_path;
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
corner_outer_radius);
int ml, mr, mt, mb;
QSize sz = sizeHint();
if (arrow_at_bottom)
// The default pen cap style Qt::SquareCap extends drawn lines one half width before the starting
// point and one half width after the ending point such that the starting and ending points are
// surrounded by drawn pixels in both dimensions instead of just for the width. This is a
// reasonable default but we need to draw lines precisely, and this behavior causes problems when
// drawing lines of length and width 1 (which we do for the arrow interior, and also the arrow
// border when border_full_width is 1). For those lines to correctly end up as a pixel we would
// need to offset the start and end points by 0.5 inward. However, doing that would lead them to
// be at the same point, and if the endpoints of a line are the same Qt simply doesn't draw it
// regardless of the cap style.
//
// Using Qt::FlatCap instead fixes the issue.
m_pixmap = QPixmap(size_hint);
QPen border_pen(m_border_color, border_full_width);
border_pen.setCapStyle(Qt::FlatCap);
QPainter balloon_painter(&m_pixmap);
balloon_painter.setPen(border_pen);
balloon_painter.setBrush(palette().color(QPalette::Window));
balloon_painter.drawPath(rect_path);
QBitmap mask_bitmap(size_hint);
mask_bitmap.fill(Qt::color0);
QPen mask_pen(Qt::color1, border_full_width);
mask_pen.setCapStyle(Qt::FlatCap);
QPainter mask_painter(&mask_bitmap);
mask_painter.setPen(mask_pen);
mask_painter.setBrush(QBrush(Qt::color1));
mask_painter.drawPath(rect_path);
const bool arrow_at_bottom =
target_arrow_tip_position.y() - size_hint.height() + arrow_height >= 0;
const bool arrow_at_left =
target_arrow_tip_position.x() + size_hint.width() - arrow_tip_x_offset < screen_rect.width();
const float arrow_base_y =
arrow_at_bottom ? rect_bottom - border_half_width : rect_top + border_half_width;
const float arrow_tip_vertical_offset = arrow_at_bottom ? arrow_height : -arrow_height;
const float arrow_tip_interior_y = arrow_base_y + arrow_tip_vertical_offset;
const float arrow_tip_exterior_y =
arrow_tip_interior_y + (arrow_at_bottom ? border_full_width : -border_full_width);
const float arrow_base_left_edge_x =
arrow_at_left ? arrow_nearest_edge_x_offset :
size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width;
const float arrow_base_right_edge_x = arrow_base_left_edge_x + arrow_full_width;
const float arrow_tip_x = arrow_base_left_edge_x + arrow_half_width;
if (show_arrow == ShowArrow::Yes)
{
ml = mt = 0;
mr = sz.width() - 1;
mb = sz.height() - arrow_height - 1;
}
else
{
ml = 0;
mt = arrow_height;
mr = sz.width() - 1;
mb = sz.height() - 1;
// Drawing diagonal lines in Qt is filled with edge cases and inexplicable behavior. Getting it
// to do what you want at one border size is simple enough, but doing so flexibly is an exercise
// in futility. Some examples:
// * For some values of x, diagonal lines of width x and x+1 are drawn exactly the same.
// * When drawing a triangle where p1 and p3 have exactly the same y value, they can be drawn at
// different heights.
// * Lines of width 1 sometimes get drawn one pixel past where they should even with FlatCap,
// but only on the left side (regardless of which direction the stroke was).
//
// Instead of dealing with all that, fake it with vertical lines which are much better behaved.
// Draw a bunch of vertical lines with width 1 to form the arrow border and interior.
QPainterPath arrow_border_path;
QPainterPath arrow_interior_fill_path;
const float y_end_offset = arrow_at_bottom ? border_full_width : -border_full_width;
// Draw the arrow border and interior lines from the outside inward. Each loop iteration draws
// one pair of border lines and one pair of interior lines.
for (int i = 1; i <= arrow_half_width; i++)
{
const float x_offset_from_arrow_base_edge = i - 0.5;
const float border_y_start = arrow_base_y + (arrow_at_bottom ? i : -i);
const float border_y_end = border_y_start + y_end_offset;
const float interior_y_start = arrow_base_y;
const float interior_y_end = border_y_start;
const float left_line_x = arrow_base_left_edge_x + x_offset_from_arrow_base_edge;
const float right_line_x = arrow_base_right_edge_x - x_offset_from_arrow_base_edge;
arrow_border_path.moveTo(left_line_x, border_y_start);
arrow_border_path.lineTo(left_line_x, border_y_end);
arrow_border_path.moveTo(right_line_x, border_y_start);
arrow_border_path.lineTo(right_line_x, border_y_end);
arrow_interior_fill_path.moveTo(left_line_x, interior_y_start);
arrow_interior_fill_path.lineTo(left_line_x, interior_y_end);
arrow_interior_fill_path.moveTo(right_line_x, interior_y_start);
arrow_interior_fill_path.lineTo(right_line_x, interior_y_end);
}
// The middle border line
arrow_border_path.moveTo(arrow_tip_x, arrow_tip_interior_y);
arrow_border_path.lineTo(arrow_tip_x, arrow_tip_interior_y + y_end_offset);
// The middle interior line
arrow_interior_fill_path.moveTo(arrow_tip_x, arrow_base_y);
arrow_interior_fill_path.lineTo(arrow_tip_x, arrow_tip_interior_y);
border_pen.setWidth(1);
balloon_painter.setPen(border_pen);
balloon_painter.drawPath(arrow_border_path);
QPen arrow_interior_fill_pen(palette().color(QPalette::Window), 1);
arrow_interior_fill_pen.setCapStyle(Qt::FlatCap);
balloon_painter.setPen(arrow_interior_fill_pen);
balloon_painter.drawPath(arrow_interior_fill_path);
mask_pen.setWidth(1);
mask_painter.setPen(mask_pen);
mask_painter.drawPath(arrow_border_path);
mask_painter.drawPath(arrow_interior_fill_path);
}
QPainterPath path;
path.moveTo(ml + rect_center, mt);
if (!arrow_at_bottom && arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(ml + arrow_offset - arrow_width, mt);
path.lineTo(ml + arrow_offset, mt - arrow_height);
path.lineTo(ml + arrow_offset + arrow_width, mt);
}
move(qMax(pos.x() - arrow_offset, screen_rect.left() + 2), pos.y());
}
else if (!arrow_at_bottom && !arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(mr - arrow_offset - arrow_width, mt);
path.lineTo(mr - arrow_offset, mt - arrow_height);
path.lineTo(mr - arrow_offset + arrow_width, mt);
}
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2), pos.y());
}
path.lineTo(mr - rect_center, mt);
path.arcTo(QRect(mr - rect_center * 2, mt, rect_center * 2, rect_center * 2), 90, -90);
path.lineTo(mr, mb - rect_center);
path.arcTo(QRect(mr - rect_center * 2, mb - rect_center * 2, rect_center * 2, rect_center * 2), 0,
-90);
if (arrow_at_bottom && !arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(mr - arrow_offset + arrow_width, mb);
path.lineTo(mr - arrow_offset, mb + arrow_height);
path.lineTo(mr - arrow_offset - arrow_width, mb);
}
move(qMin(pos.x() - sh.width() + arrow_offset, screen_rect.right() - sh.width() - 2),
pos.y() - sh.height());
}
else if (arrow_at_bottom && arrow_at_left)
{
if (m_show_arrow)
{
path.lineTo(arrow_offset + arrow_width, mb);
path.lineTo(arrow_offset, mb + arrow_height);
path.lineTo(arrow_offset - arrow_width, mb);
}
move(qMax(pos.x() - arrow_offset, screen_rect.x() + 2), pos.y() - sh.height());
}
path.lineTo(ml + rect_center, mb);
path.arcTo(QRect(ml, mb - rect_center * 2, rect_center * 2, rect_center * 2), -90, -90);
path.lineTo(ml, mt + rect_center);
path.arcTo(QRect(ml, mt, rect_center * 2, rect_center * 2), 180, -90);
setMask(mask_bitmap);
// Set the mask
QBitmap bitmap(sizeHint());
bitmap.fill(Qt::color0);
QPainter painter1(&bitmap);
painter1.setPen(QPen(Qt::color1, border));
painter1.setBrush(QBrush(Qt::color1));
painter1.drawPath(path);
setMask(bitmap);
// Place the arrow tip at the target position whether the arrow tip is drawn or not
const int target_balloontip_global_x =
target_arrow_tip_position.x() - static_cast<int>(arrow_tip_x);
const int rightmost_valid_balloontip_global_x = screen_rect.width() - size_hint.width();
// If the balloon would extend off the screen, push it left or right until it's not
const int actual_balloontip_global_x =
std::max(0, std::min(rightmost_valid_balloontip_global_x, target_balloontip_global_x));
// The tip pixel should be in the middle of the control, and arrow_tip_exterior_y is at the bottom
// of that pixel. When arrow_at_bottom is true the arrow is above arrow_tip_exterior_y and so the
// tip pixel is in the right place, but when it's false the arrow is below arrow_tip_exterior_y
// so the tip pixel would be the one below that. Make this adjustment to fix that.
const int tip_pixel_adjustment = arrow_at_bottom ? 0 : 1;
const int actual_balloontip_global_y =
target_arrow_tip_position.y() - arrow_tip_exterior_y - tip_pixel_adjustment;
// Draw the border
m_pixmap = QPixmap(sz);
QPainter painter2(&m_pixmap);
painter2.setPen(QPen(m_border_color));
painter2.setBrush(palette().color(QPalette::Window));
painter2.drawPath(path);
move(actual_balloontip_global_x, actual_balloontip_global_y);
show();
}

View file

@ -3,10 +3,14 @@
#pragma once
#include <QIcon>
#include <QColor>
#include <QPixmap>
#include <QWidget>
class QPaintEvent;
class QPoint;
class QString;
class BalloonTip : public QWidget
{
Q_OBJECT
@ -21,15 +25,16 @@ public:
Yes,
No
};
static void ShowBalloon(const QIcon& icon, const QString& title, const QString& msg,
const QPoint& pos, QWidget* parent,
ShowArrow show_arrow = ShowArrow::Yes);
static void ShowBalloon(const QString& title, const QString& message,
const QPoint& target_arrow_tip_position, QWidget* parent,
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
static void HideBalloon();
BalloonTip(PrivateTag, const QIcon& icon, QString title, QString msg, QWidget* parent);
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
private:
void UpdateBoundsAndRedraw(const QPoint&, ShowArrow);
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
int border_width);
protected:
void paintEvent(QPaintEvent*) override;
@ -37,5 +42,4 @@ protected:
private:
QColor m_border_color;
QPixmap m_pixmap;
bool m_show_arrow = true;
};

View file

@ -37,7 +37,7 @@ private:
this->killTimer(*m_timer_id);
m_timer_id.reset();
BalloonTip::ShowBalloon(QIcon(), m_title, m_description,
BalloonTip::ShowBalloon(m_title, m_description,
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
}

View file

@ -28,6 +28,7 @@
#include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Bluetooth/BTReal.h"
#include "Core/NetPlayProto.h"
#include "Core/System.h"
#include "Core/WiiUtils.h"
#include "DolphinQt/Config/Mapping/MappingWindow.h"
@ -208,7 +209,7 @@ void WiimoteControllersWidget::ConnectWidgets()
void WiimoteControllersWidget::OnBluetoothPassthroughResetPressed()
{
const auto ios = IOS::HLE::GetIOS();
const auto ios = Core::System::GetInstance().GetIOS();
if (!ios)
{
@ -225,7 +226,7 @@ void WiimoteControllersWidget::OnBluetoothPassthroughResetPressed()
void WiimoteControllersWidget::OnBluetoothPassthroughSyncPressed()
{
const auto ios = IOS::HLE::GetIOS();
const auto ios = Core::System::GetInstance().GetIOS();
if (!ios)
{
@ -295,7 +296,7 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
m_wiimote_emu->setEnabled(!running);
m_wiimote_passthrough->setEnabled(!running);
const bool running_gc = running && !SConfig::GetInstance().bWii;
const bool running_gc = running && !Core::System::GetInstance().IsWii();
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;
const bool is_netplay = NetPlay::IsNetPlayRunning();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <optional>
#include <string>
#include <QDialog>
#include <QModelIndexList>
#include "Core/Core.h"
namespace Core
{
class BranchWatch;
class CPUThreadGuard;
class System;
} // namespace Core
class BranchWatchProxyModel;
class BranchWatchTableModel;
class CodeWidget;
class QAction;
class QMenu;
class QPoint;
class QPushButton;
class QStatusBar;
class QTableView;
class QTimer;
class QToolBar;
class QWidget;
namespace BranchWatchTableModelColumn
{
enum EnumType : int;
}
namespace BranchWatchTableModelUserRole
{
enum EnumType : int;
}
class BranchWatchDialog : public QDialog
{
Q_OBJECT
using Column = BranchWatchTableModelColumn::EnumType;
using UserRole = BranchWatchTableModelUserRole::EnumType;
public:
explicit BranchWatchDialog(Core::System& system, Core::BranchWatch& branch_watch,
CodeWidget* code_widget, QWidget* parent = nullptr);
void done(int r) override;
int exec() override;
void open() override;
private:
void OnStartPause(bool checked);
void OnClearBranchWatch();
void OnSave();
void OnSaveAs();
void OnLoad();
void OnLoadFrom();
void OnCodePathWasTaken();
void OnCodePathNotTaken();
void OnBranchWasOverwritten();
void OnBranchNotOverwritten();
void OnWipeRecentHits();
void OnWipeInspection();
void OnTimeout();
void OnEmulationStateChanged(Core::State new_state);
void OnHelp();
void OnToggleAutoSave(bool checked);
void OnHideShowControls(bool checked);
void OnToggleIgnoreApploader(bool checked);
void OnTableClicked(const QModelIndex& index);
void OnTableContextMenu(const QPoint& pos);
void OnTableHeaderContextMenu(const QPoint& pos);
void OnTableDelete(QModelIndexList index_list);
void OnTableDeleteKeypress();
void OnTableSetBLR(QModelIndexList index_list);
void OnTableSetNOP(QModelIndexList index_list);
void OnTableCopyAddress(QModelIndexList index_list);
public:
// TODO: Step doesn't cause EmulationStateChanged to be emitted, so it has to call this manually.
void Update();
// TODO: There seems to be a lack of a ubiquitous signal for when symbols change.
void UpdateSymbols();
private:
void UpdateStatus();
void Save(const Core::CPUThreadGuard& guard, const std::string& filepath);
void Load(const Core::CPUThreadGuard& guard, const std::string& filepath);
void AutoSave(const Core::CPUThreadGuard& guard);
Core::System& m_system;
Core::BranchWatch& m_branch_watch;
CodeWidget* m_code_widget;
QPushButton *m_btn_start_pause, *m_btn_clear_watch, *m_btn_path_was_taken, *m_btn_path_not_taken,
*m_btn_was_overwritten, *m_btn_not_overwritten, *m_btn_wipe_recent_hits;
QAction* m_act_autosave;
QMenu* m_mnu_column_visibility;
QToolBar* m_control_toolbar;
QTableView* m_table_view;
BranchWatchProxyModel* m_table_proxy;
BranchWatchTableModel* m_table_model;
QStatusBar* m_status_bar;
QTimer* m_timer;
std::optional<std::string> m_autosave_filepath;
};

View file

@ -0,0 +1,502 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Debugger/BranchWatchTableModel.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <QBrush>
#include "Common/Assert.h"
#include "Common/GekkoDisassembler.h"
#include "Core/Debugger/BranchWatch.h"
#include "Core/PowerPC/PPCSymbolDB.h"
QVariant BranchWatchTableModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
switch (role)
{
case Qt::DisplayRole:
return DisplayRoleData(index);
case Qt::FontRole:
return FontRoleData(index);
case Qt::TextAlignmentRole:
return TextAlignmentRoleData(index);
case Qt::ForegroundRole:
return ForegroundRoleData(index);
case UserRole::ClickRole:
return ClickRoleData(index);
case UserRole::SortRole:
return SortRoleData(index);
}
return QVariant();
}
QVariant BranchWatchTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical || role != Qt::DisplayRole)
return QVariant();
static constexpr std::array<const char*, Column::NumberOfColumns> headers = {
QT_TR_NOOP("Instr."), QT_TR_NOOP("Cond."),
QT_TR_NOOP("Origin"), QT_TR_NOOP("Destination"),
QT_TR_NOOP("Recent Hits"), QT_TR_NOOP("Total Hits"),
QT_TR_NOOP("Origin Symbol"), QT_TR_NOOP("Destination Symbol")};
return tr(headers[section]);
}
int BranchWatchTableModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return static_cast<int>(m_branch_watch.GetSelection().size());
}
int BranchWatchTableModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return Column::NumberOfColumns;
}
bool BranchWatchTableModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (parent.isValid() || row < 0)
return false;
if (count <= 0)
return true;
auto& selection = m_branch_watch.GetSelection();
beginRemoveRows(parent, row, row + count - 1); // Last is inclusive in Qt!
selection.erase(selection.begin() + row, selection.begin() + row + count);
m_symbol_list.remove(row, count);
endRemoveRows();
return true;
}
void BranchWatchTableModel::OnClearBranchWatch(const Core::CPUThreadGuard& guard)
{
emit layoutAboutToBeChanged();
m_branch_watch.Clear(guard);
m_symbol_list.clear();
emit layoutChanged();
}
void BranchWatchTableModel::OnCodePathWasTaken(const Core::CPUThreadGuard& guard)
{
emit layoutAboutToBeChanged();
m_branch_watch.IsolateHasExecuted(guard);
PrefetchSymbols();
emit layoutChanged();
}
void BranchWatchTableModel::OnCodePathNotTaken(const Core::CPUThreadGuard& guard)
{
emit layoutAboutToBeChanged();
m_branch_watch.IsolateNotExecuted(guard);
PrefetchSymbols();
emit layoutChanged();
}
void BranchWatchTableModel::OnBranchWasOverwritten(const Core::CPUThreadGuard& guard)
{
emit layoutAboutToBeChanged();
m_branch_watch.IsolateWasOverwritten(guard);
PrefetchSymbols();
emit layoutChanged();
}
void BranchWatchTableModel::OnBranchNotOverwritten(const Core::CPUThreadGuard& guard)
{
emit layoutAboutToBeChanged();
m_branch_watch.IsolateNotOverwritten(guard);
PrefetchSymbols();
emit layoutChanged();
}
void BranchWatchTableModel::OnWipeRecentHits()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
static const QList<int> roles = {Qt::DisplayRole};
m_branch_watch.UpdateHitsSnapshot();
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::RecentHits), createIndex(last, Column::RecentHits),
roles);
}
void BranchWatchTableModel::OnWipeInspection()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole};
m_branch_watch.ClearSelectionInspection();
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::Origin), createIndex(last, Column::Destination), roles);
emit dataChanged(createIndex(0, Column::OriginSymbol), createIndex(last, Column::DestinSymbol),
roles);
}
void BranchWatchTableModel::OnDelete(QModelIndexList index_list)
{
std::sort(index_list.begin(), index_list.end());
// TODO C++20: std::ranges::reverse_view
for (auto iter = index_list.rbegin(); iter != index_list.rend(); ++iter)
{
if (!iter->isValid())
continue;
removeRow(iter->row());
}
}
void BranchWatchTableModel::Save(const Core::CPUThreadGuard& guard, std::FILE* file) const
{
m_branch_watch.Save(guard, file);
}
void BranchWatchTableModel::Load(const Core::CPUThreadGuard& guard, std::FILE* file)
{
emit layoutAboutToBeChanged();
m_branch_watch.Load(guard, file);
PrefetchSymbols();
emit layoutChanged();
}
void BranchWatchTableModel::UpdateSymbols()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
static const QList<int> roles = {Qt::DisplayRole};
PrefetchSymbols();
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::OriginSymbol), createIndex(last, Column::DestinSymbol),
roles);
}
void BranchWatchTableModel::UpdateHits()
{
const int row_count = rowCount();
if (row_count <= 0)
return;
static const QList<int> roles = {Qt::DisplayRole};
const int last = row_count - 1;
emit dataChanged(createIndex(0, Column::RecentHits), createIndex(last, Column::TotalHits), roles);
}
void BranchWatchTableModel::SetInspected(const QModelIndex& index)
{
const int row = index.row();
switch (index.column())
{
case Column::Origin:
SetOriginInspected(m_branch_watch.GetSelection()[row].collection_ptr->first.origin_addr);
return;
case Column::Destination:
SetDestinInspected(m_branch_watch.GetSelection()[row].collection_ptr->first.destin_addr, false);
return;
case Column::OriginSymbol:
SetSymbolInspected(m_symbol_list[row].origin_addr.value<u32>(), false);
return;
case Column::DestinSymbol:
SetSymbolInspected(m_symbol_list[row].destin_addr.value<u32>(), false);
return;
}
}
void BranchWatchTableModel::SetOriginInspected(u32 origin_addr)
{
using Inspection = Core::BranchWatchSelectionInspection;
static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole};
const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection();
for (std::size_t i = 0; i < selection.size(); ++i)
{
if (selection[i].collection_ptr->first.origin_addr != origin_addr)
continue;
m_branch_watch.SetSelectedInspected(i, Inspection::SetOriginNOP);
const QModelIndex index = createIndex(static_cast<int>(i), Column::Origin);
emit dataChanged(index, index, roles);
}
}
void BranchWatchTableModel::SetDestinInspected(u32 destin_addr, bool nested)
{
using Inspection = Core::BranchWatchSelectionInspection;
static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole};
const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection();
for (std::size_t i = 0; i < selection.size(); ++i)
{
if (selection[i].collection_ptr->first.destin_addr != destin_addr)
continue;
m_branch_watch.SetSelectedInspected(i, Inspection::SetDestinBLR);
const QModelIndex index = createIndex(static_cast<int>(i), Column::Destination);
emit dataChanged(index, index, roles);
}
if (nested)
return;
SetSymbolInspected(destin_addr, true);
}
void BranchWatchTableModel::SetSymbolInspected(u32 symbol_addr, bool nested)
{
using Inspection = Core::BranchWatchSelectionInspection;
static const QList<int> roles = {Qt::FontRole, Qt::ForegroundRole};
for (qsizetype i = 0; i < m_symbol_list.size(); ++i)
{
const SymbolListValueType& value = m_symbol_list[i];
if (value.origin_addr.isValid() && value.origin_addr.value<u32>() == symbol_addr)
{
m_branch_watch.SetSelectedInspected(i, Inspection::SetOriginSymbolBLR);
const QModelIndex index = createIndex(i, Column::OriginSymbol);
emit dataChanged(index, index, roles);
}
if (value.destin_addr.isValid() && value.destin_addr.value<u32>() == symbol_addr)
{
m_branch_watch.SetSelectedInspected(i, Inspection::SetDestinSymbolBLR);
const QModelIndex index = createIndex(i, Column::DestinSymbol);
emit dataChanged(index, index, roles);
}
}
if (nested)
return;
SetDestinInspected(symbol_addr, true);
}
void BranchWatchTableModel::PrefetchSymbols()
{
if (m_branch_watch.GetRecordingPhase() != Core::BranchWatch::Phase::Reduction)
return;
const Core::BranchWatch::Selection& selection = m_branch_watch.GetSelection();
m_symbol_list.clear();
m_symbol_list.reserve(selection.size());
for (const Core::BranchWatch::Selection::value_type& value : selection)
{
const Core::BranchWatch::Collection::value_type* const kv = value.collection_ptr;
m_symbol_list.emplace_back(g_symbolDB.GetSymbolFromAddr(kv->first.origin_addr),
g_symbolDB.GetSymbolFromAddr(kv->first.destin_addr));
}
}
static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v)
{
if (symbol_name_v.isValid())
return symbol_name_v;
return QStringLiteral(" --- ");
}
static QString GetInstructionMnemonic(u32 hex)
{
const std::string disas = Common::GekkoDisassembler::Disassemble(hex, 0);
const std::string::size_type split = disas.find('\t');
// I wish I could disassemble just the mnemonic!
if (split == std::string::npos)
return QString::fromStdString(disas);
return QString::fromLatin1(disas.data(), split);
}
static bool BranchIsUnconditional(UGeckoInstruction inst)
{
if (inst.OPCD == 18) // bx
return true;
// If BranchWatch is doing its job, the input will be only bcx, bclrx, and bcctrx instructions.
DEBUG_ASSERT(inst.OPCD == 16 || (inst.OPCD == 19 && (inst.SUBOP10 == 16 || inst.SUBOP10 == 528)));
if ((inst.BO & 0b10100) == 0b10100) // 1z1zz - Branch always
return true;
return false;
}
static QString GetConditionString(const Core::BranchWatch::Selection::value_type& value,
const Core::BranchWatch::Collection::value_type* kv)
{
if (value.condition == false)
return BranchWatchTableModel::tr("false");
if (BranchIsUnconditional(kv->first.original_inst))
return QStringLiteral("");
return BranchWatchTableModel::tr("true");
}
QVariant BranchWatchTableModel::DisplayRoleData(const QModelIndex& index) const
{
switch (index.column())
{
case Column::OriginSymbol:
return GetValidSymbolStringVariant(m_symbol_list[index.row()].origin_name);
case Column::DestinSymbol:
return GetValidSymbolStringVariant(m_symbol_list[index.row()].destin_name);
}
const Core::BranchWatch::Selection::value_type& value =
m_branch_watch.GetSelection()[index.row()];
const Core::BranchWatch::Collection::value_type* kv = value.collection_ptr;
switch (index.column())
{
case Column::Instruction:
return GetInstructionMnemonic(kv->first.original_inst.hex);
case Column::Condition:
return GetConditionString(value, kv);
case Column::Origin:
return QString::number(kv->first.origin_addr, 16);
case Column::Destination:
return QString::number(kv->first.destin_addr, 16);
case Column::RecentHits:
return QString::number(kv->second.total_hits - kv->second.hits_snapshot);
case Column::TotalHits:
return QString::number(kv->second.total_hits);
}
return QVariant();
}
QVariant BranchWatchTableModel::FontRoleData(const QModelIndex& index) const
{
m_font.setBold([&]() -> bool {
switch (index.column())
{
using Inspection = Core::BranchWatchSelectionInspection;
case Column::Origin:
return (m_branch_watch.GetSelection()[index.row()].inspection & Inspection::SetOriginNOP) !=
Inspection{};
case Column::Destination:
return (m_branch_watch.GetSelection()[index.row()].inspection & Inspection::SetDestinBLR) !=
Inspection{};
case Column::OriginSymbol:
return (m_branch_watch.GetSelection()[index.row()].inspection &
Inspection::SetOriginSymbolBLR) != Inspection{};
case Column::DestinSymbol:
return (m_branch_watch.GetSelection()[index.row()].inspection &
Inspection::SetDestinSymbolBLR) != Inspection{};
}
// Importantly, this code path avoids subscripting the selection to get an inspection value.
return false;
}());
return m_font;
}
QVariant BranchWatchTableModel::TextAlignmentRoleData(const QModelIndex& index) const
{
// Qt enums become QFlags when operators are used. QVariant's constructors don't support QFlags.
switch (index.column())
{
case Column::Condition:
case Column::Origin:
case Column::Destination:
return Qt::AlignCenter;
case Column::RecentHits:
case Column::TotalHits:
return QVariant::fromValue(Qt::AlignRight | Qt::AlignVCenter);
case Column::Instruction:
case Column::OriginSymbol:
case Column::DestinSymbol:
return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter);
}
return QVariant();
}
QVariant BranchWatchTableModel::ForegroundRoleData(const QModelIndex& index) const
{
switch (index.column())
{
using Inspection = Core::BranchWatchSelectionInspection;
case Column::Origin:
{
const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection;
return (inspection & Inspection::SetOriginNOP) != Inspection{} ? QBrush(Qt::red) : QVariant();
}
case Column::Destination:
{
const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection;
return (inspection & Inspection::SetDestinBLR) != Inspection{} ? QBrush(Qt::red) : QVariant();
}
case Column::OriginSymbol:
{
const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection;
return (inspection & Inspection::SetOriginSymbolBLR) != Inspection{} ? QBrush(Qt::red) :
QVariant();
}
case Column::DestinSymbol:
{
const Inspection inspection = m_branch_watch.GetSelection()[index.row()].inspection;
return (inspection & Inspection::SetDestinSymbolBLR) != Inspection{} ? QBrush(Qt::red) :
QVariant();
}
}
// Importantly, this code path avoids subscripting the selection to get an inspection value.
return QVariant();
}
QVariant BranchWatchTableModel::ClickRoleData(const QModelIndex& index) const
{
switch (index.column())
{
case Column::OriginSymbol:
return m_symbol_list[index.row()].origin_addr;
case Column::DestinSymbol:
return m_symbol_list[index.row()].destin_addr;
}
const Core::BranchWatch::Collection::value_type* kv =
m_branch_watch.GetSelection()[index.row()].collection_ptr;
switch (index.column())
{
case Column::Instruction:
return kv->first.original_inst.hex;
case Column::Origin:
return kv->first.origin_addr;
case Column::Destination:
return kv->first.destin_addr;
}
return QVariant();
}
// 0 == false, 1 == true, 2 == unconditional
static int GetConditionInteger(const Core::BranchWatch::Selection::value_type& value,
const Core::BranchWatch::Collection::value_type* kv)
{
if (value.condition == false)
return 0;
if (BranchIsUnconditional(kv->first.original_inst))
return 2;
return 1;
}
QVariant BranchWatchTableModel::SortRoleData(const QModelIndex& index) const
{
switch (index.column())
{
case Column::OriginSymbol:
return m_symbol_list[index.row()].origin_name;
case Column::DestinSymbol:
return m_symbol_list[index.row()].destin_name;
}
const Core::BranchWatch::Selection::value_type& selection_value =
m_branch_watch.GetSelection()[index.row()];
const Core::BranchWatch::Collection::value_type* kv = selection_value.collection_ptr;
switch (index.column())
{
// QVariant's ctor only supports (unsigned) int and (unsigned) long long for some stupid reason.
// std::size_t is unsigned long on some platforms, which results in an ambiguous conversion.
case Column::Instruction:
return GetInstructionMnemonic(kv->first.original_inst.hex);
case Column::Condition:
return GetConditionInteger(selection_value, kv);
case Column::Origin:
return kv->first.origin_addr;
case Column::Destination:
return kv->first.destin_addr;
case Column::RecentHits:
return qulonglong{kv->second.total_hits - kv->second.hits_snapshot};
case Column::TotalHits:
return qulonglong{kv->second.total_hits};
}
return QVariant();
}

View file

@ -0,0 +1,119 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdio>
#include <QAbstractTableModel>
#include <QFont>
#include <QList>
#include <QVariant>
#include "Common/SymbolDB.h"
namespace Core
{
class BranchWatch;
class CPUThreadGuard;
class System;
} // namespace Core
namespace BranchWatchTableModelColumn
{
enum EnumType : int
{
Instruction = 0,
Condition,
Origin,
Destination,
RecentHits,
TotalHits,
OriginSymbol,
DestinSymbol,
NumberOfColumns,
};
}
namespace BranchWatchTableModelUserRole
{
enum EnumType : int
{
ClickRole = Qt::UserRole,
SortRole,
};
}
struct BranchWatchTableModelSymbolListValueType
{
explicit BranchWatchTableModelSymbolListValueType(const Common::Symbol* const origin_symbol,
const Common::Symbol* const destin_symbol)
: origin_name(origin_symbol ? QString::fromStdString(origin_symbol->name) : QVariant{}),
origin_addr(origin_symbol ? origin_symbol->address : QVariant{}),
destin_name(destin_symbol ? QString::fromStdString(destin_symbol->name) : QVariant{}),
destin_addr(destin_symbol ? destin_symbol->address : QVariant{})
{
}
QVariant origin_name, origin_addr;
QVariant destin_name, destin_addr;
};
class BranchWatchTableModel final : public QAbstractTableModel
{
Q_OBJECT
public:
using Column = BranchWatchTableModelColumn::EnumType;
using UserRole = BranchWatchTableModelUserRole::EnumType;
using SymbolListValueType = BranchWatchTableModelSymbolListValueType;
using SymbolList = QList<SymbolListValueType>;
explicit BranchWatchTableModel(Core::System& system, Core::BranchWatch& branch_watch,
QObject* parent = nullptr)
: QAbstractTableModel(parent), m_system(system), m_branch_watch(branch_watch)
{
}
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex{}) const override;
int columnCount(const QModelIndex& parent = QModelIndex{}) const override;
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex{}) override;
void setFont(const QFont& font) { m_font = font; }
void OnClearBranchWatch(const Core::CPUThreadGuard& guard);
void OnCodePathWasTaken(const Core::CPUThreadGuard& guard);
void OnCodePathNotTaken(const Core::CPUThreadGuard& guard);
void OnBranchWasOverwritten(const Core::CPUThreadGuard& guard);
void OnBranchNotOverwritten(const Core::CPUThreadGuard& guard);
void OnWipeRecentHits();
void OnWipeInspection();
void OnDelete(QModelIndexList index_list);
void Save(const Core::CPUThreadGuard& guard, std::FILE* file) const;
void Load(const Core::CPUThreadGuard& guard, std::FILE* file);
void UpdateSymbols();
void UpdateHits();
void SetInspected(const QModelIndex& index);
const SymbolList& GetSymbolList() const { return m_symbol_list; }
private:
void SetOriginInspected(u32 origin_addr);
void SetDestinInspected(u32 destin_addr, bool nested);
void SetSymbolInspected(u32 symbol_addr, bool nested);
void PrefetchSymbols();
[[nodiscard]] QVariant DisplayRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant FontRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant TextAlignmentRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant ForegroundRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant ClickRoleData(const QModelIndex& index) const;
[[nodiscard]] QVariant SortRoleData(const QModelIndex& index) const;
Core::System& m_system;
Core::BranchWatch& m_branch_watch;
SymbolList m_symbol_list;
mutable QFont m_font;
};

View file

@ -1,673 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Debugger/CodeDiffDialog.h"
#include <algorithm>
#include <sstream>
#include <string>
#include <vector>
#include <QCheckBox>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QPushButton>
#include <QStyleHints>
#include <QTableWidget>
#include <QVBoxLayout>
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/HW/CPU.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/PowerPC/Profiler.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/CodeWidget.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/Settings.h"
static const QString RECORD_BUTTON_STYLESHEET = QStringLiteral(
"QPushButton:checked { background-color: rgb(150, 0, 0); border-style: solid;"
"padding: 0px; border-width: 3px; border-color: rgb(150,0,0); color: rgb(255, 255, 255);}");
CodeDiffDialog::CodeDiffDialog(CodeWidget* parent) : QDialog(parent), m_code_widget(parent)
{
setWindowTitle(tr("Code Diff Tool"));
CreateWidgets();
auto& settings = Settings::GetQSettings();
restoreGeometry(settings.value(QStringLiteral("diffdialog/geometry")).toByteArray());
ConnectWidgets();
}
void CodeDiffDialog::reject()
{
ClearData();
auto& settings = Settings::GetQSettings();
settings.setValue(QStringLiteral("diffdialog/geometry"), saveGeometry());
QDialog::reject();
}
void CodeDiffDialog::CreateWidgets()
{
bool running = Core::GetState() != Core::State::Uninitialized;
auto* btns_layout = new QGridLayout;
m_exclude_btn = new QPushButton(tr("Code did not get executed"));
m_include_btn = new QPushButton(tr("Code has been executed"));
m_record_btn = new QPushButton(tr("Start Recording"));
m_record_btn->setCheckable(true);
m_record_btn->setStyleSheet(RECORD_BUTTON_STYLESHEET);
m_record_btn->setEnabled(running);
m_exclude_btn->setEnabled(false);
m_include_btn->setEnabled(false);
btns_layout->addWidget(m_exclude_btn, 0, 0);
btns_layout->addWidget(m_include_btn, 0, 1);
btns_layout->addWidget(m_record_btn, 0, 2);
auto* labels_layout = new QHBoxLayout;
m_exclude_size_label = new QLabel(tr("Excluded: 0"));
m_include_size_label = new QLabel(tr("Included: 0"));
btns_layout->addWidget(m_exclude_size_label, 1, 0);
btns_layout->addWidget(m_include_size_label, 1, 1);
m_matching_results_table = new QTableWidget();
m_matching_results_table->setColumnCount(5);
m_matching_results_table->setHorizontalHeaderLabels(
{tr("Address"), tr("Total Hits"), tr("Hits"), tr("Symbol"), tr("Inspected")});
m_matching_results_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_matching_results_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_matching_results_table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_matching_results_table->setContextMenuPolicy(Qt::CustomContextMenu);
m_matching_results_table->setColumnWidth(0, 60);
m_matching_results_table->setColumnWidth(1, 60);
m_matching_results_table->setColumnWidth(2, 4);
m_matching_results_table->setColumnWidth(3, 210);
m_matching_results_table->setColumnWidth(4, 65);
m_matching_results_table->setCornerButtonEnabled(false);
m_autosave_check = new QCheckBox(tr("Auto Save"));
m_save_btn = new QPushButton(tr("Save"));
m_save_btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_save_btn->setEnabled(running);
m_load_btn = new QPushButton(tr("Load"));
m_load_btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_load_btn->setEnabled(running);
m_reset_btn = new QPushButton(tr("Reset All"));
m_reset_btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_help_btn = new QPushButton(tr("Help"));
m_help_btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
auto* bottom_controls_layout = new QHBoxLayout;
bottom_controls_layout->addWidget(m_reset_btn, 0, Qt::AlignLeft);
bottom_controls_layout->addStretch();
bottom_controls_layout->addWidget(m_autosave_check, 0, Qt::AlignRight);
bottom_controls_layout->addWidget(m_save_btn, 0, Qt::AlignRight);
bottom_controls_layout->addWidget(m_load_btn, 0, Qt::AlignRight);
bottom_controls_layout->addWidget(m_help_btn, 0, Qt::AlignRight);
auto* layout = new QVBoxLayout();
layout->addLayout(btns_layout);
layout->addLayout(labels_layout);
layout->addWidget(m_matching_results_table);
layout->addLayout(bottom_controls_layout);
setLayout(layout);
resize(515, 400);
}
void CodeDiffDialog::ConnectWidgets()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this,
[this](Qt::ColorScheme colorScheme) {
m_record_btn->setStyleSheet(RECORD_BUTTON_STYLESHEET);
});
#endif
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[this](Core::State state) { UpdateButtons(state != Core::State::Uninitialized); });
connect(m_record_btn, &QPushButton::toggled, this, &CodeDiffDialog::OnRecord);
connect(m_include_btn, &QPushButton::pressed, [this]() { Update(UpdateType::Include); });
connect(m_exclude_btn, &QPushButton::pressed, [this]() { Update(UpdateType::Exclude); });
connect(m_matching_results_table, &QTableWidget::itemClicked, [this]() { OnClickItem(); });
connect(m_save_btn, &QPushButton::pressed, this, &CodeDiffDialog::SaveDataBackup);
connect(m_load_btn, &QPushButton::pressed, this, &CodeDiffDialog::LoadDataBackup);
connect(m_reset_btn, &QPushButton::pressed, this, &CodeDiffDialog::ClearData);
connect(m_help_btn, &QPushButton::pressed, this, &CodeDiffDialog::InfoDisp);
connect(m_matching_results_table, &CodeDiffDialog::customContextMenuRequested, this,
&CodeDiffDialog::OnContextMenu);
}
void CodeDiffDialog::OnClickItem()
{
UpdateItem();
auto address = m_matching_results_table->currentItem()->data(Qt::UserRole).toUInt();
m_code_widget->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
}
void CodeDiffDialog::SaveDataBackup()
{
if (Core::GetState() == Core::State::Uninitialized)
{
ModalMessageBox::information(this, tr("Code Diff Tool"),
tr("Emulation must be started before saving a file."));
return;
}
if (m_include.empty())
return;
std::string filename =
File::GetUserPath(D_LOGS_IDX) + SConfig::GetInstance().GetGameID() + "_CodeDiff.txt";
File::IOFile f(filename, "w");
if (!f)
{
ModalMessageBox::information(
this, tr("Code Diff Tool"),
tr("Failed to save file to: %1").arg(QString::fromStdString(filename)));
return;
}
// Copy list of BLR tested functions:
std::set<u32> address_blr;
for (int i = 0; i < m_matching_results_table->rowCount(); i++)
{
if (m_matching_results_table->item(i, 4)->text() == QStringLiteral("X"))
address_blr.insert(m_matching_results_table->item(i, 4)->data(Qt::UserRole).toUInt());
}
for (const auto& line : m_include)
{
bool blr = address_blr.contains(line.addr);
f.WriteString(
fmt::format("{} {} {} {:d} {}\n", line.addr, line.hits, line.total_hits, blr, line.symbol));
}
}
void CodeDiffDialog::LoadDataBackup()
{
if (Core::GetState() == Core::State::Uninitialized)
{
ModalMessageBox::information(this, tr("Code Diff Tool"),
tr("Emulation must be started before loading a file."));
return;
}
if (g_symbolDB.IsEmpty())
{
ModalMessageBox::warning(
this, tr("Code Diff Tool"),
tr("Symbol map not found.\n\nIf one does not exist, you can generate one from "
"the Menu bar:\nSymbols -> Generate Symbols From ->\n\tAddress | Signature "
"Database | RSO Modules"));
return;
}
std::string filename =
File::GetUserPath(D_LOGS_IDX) + SConfig::GetInstance().GetGameID() + "_CodeDiff.txt";
File::IOFile f(filename, "r");
if (!f)
{
ModalMessageBox::information(
this, tr("Code Diff Tool"),
tr("Failed to find or open file: %1").arg(QString::fromStdString(filename)));
return;
};
ClearData();
std::set<u32> blr_addresses;
char line[512];
while (fgets(line, 512, f.GetHandle()))
{
bool blr = false;
Diff temp;
std::istringstream iss(line);
iss.imbue(std::locale::classic());
iss >> temp.addr >> temp.hits >> temp.total_hits >> blr >> std::ws;
std::getline(iss, temp.symbol);
if (blr)
blr_addresses.insert(temp.addr);
m_include.push_back(std::move(temp));
}
Update(UpdateType::Backup);
for (int i = 0; i < m_matching_results_table->rowCount(); i++)
{
if (blr_addresses.contains(m_matching_results_table->item(i, 4)->data(Qt::UserRole).toUInt()))
MarkRowBLR(i);
}
}
void CodeDiffDialog::ClearData()
{
if (m_record_btn->isChecked())
m_record_btn->toggle();
ClearBlockCache();
m_matching_results_table->clear();
m_matching_results_table->setRowCount(0);
m_matching_results_table->setHorizontalHeaderLabels(
{tr("Address"), tr("Total Hits"), tr("Hits"), tr("Symbol"), tr("Inspected")});
m_matching_results_table->setEditTriggers(QAbstractItemView::EditTrigger::NoEditTriggers);
m_exclude_size_label->setText(tr("Excluded: %1").arg(0));
m_include_size_label->setText(tr("Included: %1").arg(0));
m_exclude_btn->setEnabled(false);
m_include_btn->setEnabled(false);
m_include_active = false;
// Swap is used instead of clear for efficiency in the case of huge m_include/m_exclude
std::vector<Diff>().swap(m_include);
std::vector<Diff>().swap(m_exclude);
Core::System::GetInstance().GetJitInterface().SetProfilingState(
JitInterface::ProfilingState::Disabled);
}
void CodeDiffDialog::ClearBlockCache()
{
Core::State old_state = Core::GetState();
if (old_state == Core::State::Running)
Core::SetState(Core::State::Paused, false);
Core::System::GetInstance().GetJitInterface().ClearCache();
if (old_state == Core::State::Running)
Core::SetState(Core::State::Running);
}
void CodeDiffDialog::OnRecord(bool enabled)
{
if (m_failed_requirements)
{
m_failed_requirements = false;
return;
}
if (Core::GetState() == Core::State::Uninitialized)
{
ModalMessageBox::information(this, tr("Code Diff Tool"),
tr("Emulation must be started to record."));
m_failed_requirements = true;
m_record_btn->setChecked(false);
return;
}
if (g_symbolDB.IsEmpty())
{
ModalMessageBox::warning(
this, tr("Code Diff Tool"),
tr("Symbol map not found.\n\nIf one does not exist, you can generate one from "
"the Menu bar:\nSymbols -> Generate Symbols From ->\n\tAddress | Signature "
"Database | RSO Modules"));
m_failed_requirements = true;
m_record_btn->setChecked(false);
return;
}
JitInterface::ProfilingState state;
if (enabled)
{
ClearBlockCache();
m_record_btn->setText(tr("Stop Recording"));
state = JitInterface::ProfilingState::Enabled;
m_exclude_btn->setEnabled(true);
m_include_btn->setEnabled(true);
}
else
{
ClearBlockCache();
m_record_btn->setText(tr("Start Recording"));
state = JitInterface::ProfilingState::Disabled;
m_exclude_btn->setEnabled(false);
m_include_btn->setEnabled(false);
}
m_record_btn->update();
Core::System::GetInstance().GetJitInterface().SetProfilingState(state);
}
void CodeDiffDialog::OnInclude()
{
const auto recorded_symbols = CalculateSymbolsFromProfile();
if (recorded_symbols.empty())
return;
if (m_include.empty() && m_exclude.empty())
{
m_include = recorded_symbols;
m_include_active = true;
}
else if (m_include.empty())
{
// If include becomes empty after having items on it, don't refill it until after a reset.
if (m_include_active)
return;
// If we are building include for the first time and we have an exlcude list, then include =
// recorded - excluded.
m_include = recorded_symbols;
RemoveMatchingSymbolsFromIncludes(m_exclude);
m_include_active = true;
}
else
{
// If include already exists, keep items that are in both include and recorded. Exclude list
// becomes irrelevant.
RemoveMissingSymbolsFromIncludes(recorded_symbols);
}
}
void CodeDiffDialog::OnExclude()
{
const auto recorded_symbols = CalculateSymbolsFromProfile();
if (m_include.empty() && m_exclude.empty())
{
m_exclude = recorded_symbols;
}
else if (m_include.empty())
{
// If there is only an exclude list, update it.
for (auto& iter : recorded_symbols)
{
auto pos = std::lower_bound(m_exclude.begin(), m_exclude.end(), iter.symbol);
if (pos == m_exclude.end() || pos->symbol != iter.symbol)
m_exclude.insert(pos, iter);
}
}
else
{
// If include already exists, the exclude list will have been used to trim it, so the exclude
// list is now irrelevant, as anythng not on the include list is effectively excluded.
// Exclude/subtract recorded items from the include list.
RemoveMatchingSymbolsFromIncludes(recorded_symbols);
}
}
std::vector<Diff> CodeDiffDialog::CalculateSymbolsFromProfile() const
{
Profiler::ProfileStats prof_stats;
auto& blockstats = prof_stats.block_stats;
Core::System::GetInstance().GetJitInterface().GetProfileResults(&prof_stats);
std::vector<Diff> current;
current.reserve(20000);
// Convert blockstats to smaller struct Diff. Exclude repeat functions via symbols.
for (const auto& iter : blockstats)
{
std::string symbol = g_symbolDB.GetDescription(iter.addr);
if (!std::any_of(current.begin(), current.end(),
[&symbol](const Diff& v) { return v.symbol == symbol; }))
{
current.push_back(Diff{
.addr = iter.addr,
.symbol = std::move(symbol),
.hits = static_cast<u32>(iter.run_count),
.total_hits = static_cast<u32>(iter.run_count),
});
}
}
std::sort(current.begin(), current.end(),
[](const Diff& v1, const Diff& v2) { return (v1.symbol < v2.symbol); });
return current;
}
void CodeDiffDialog::RemoveMissingSymbolsFromIncludes(const std::vector<Diff>& symbol_diff)
{
m_include.erase(std::remove_if(m_include.begin(), m_include.end(),
[&](const Diff& v) {
auto arg = std::none_of(
symbol_diff.begin(), symbol_diff.end(), [&](const Diff& p) {
return p.symbol == v.symbol || p.addr == v.addr;
});
return arg;
}),
m_include.end());
for (auto& original_includes : m_include)
{
auto pos = std::lower_bound(symbol_diff.begin(), symbol_diff.end(), original_includes.symbol);
if (pos != symbol_diff.end() &&
(pos->symbol == original_includes.symbol || pos->addr == original_includes.addr))
{
original_includes.total_hits += pos->hits;
original_includes.hits = pos->hits;
}
}
}
void CodeDiffDialog::RemoveMatchingSymbolsFromIncludes(const std::vector<Diff>& symbol_list)
{
m_include.erase(std::remove_if(m_include.begin(), m_include.end(),
[&](const Diff& i) {
return std::any_of(
symbol_list.begin(), symbol_list.end(), [&](const Diff& s) {
return i.symbol == s.symbol || i.addr == s.addr;
});
}),
m_include.end());
}
void CodeDiffDialog::Update(UpdateType type)
{
// Wrap everything in a pause
Core::State old_state = Core::GetState();
if (old_state == Core::State::Running)
Core::SetState(Core::State::Paused, false);
// Main process
if (type == UpdateType::Include)
{
OnInclude();
}
else if (type == UpdateType::Exclude)
{
OnExclude();
}
if (type != UpdateType::Backup && m_autosave_check->isChecked() && !m_include.empty())
SaveDataBackup();
const auto create_item = [](const QString& string = {}, const u32 address = 0x00000000) {
QTableWidgetItem* item = new QTableWidgetItem(string);
item->setData(Qt::UserRole, address);
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
return item;
};
int i = 0;
m_matching_results_table->clear();
m_matching_results_table->setRowCount(i);
m_matching_results_table->setHorizontalHeaderLabels(
{tr("Address"), tr("Total Hits"), tr("Hits"), tr("Symbol"), tr("Inspected")});
for (auto& iter : m_include)
{
m_matching_results_table->setRowCount(i + 1);
QString fix_sym = QString::fromStdString(iter.symbol);
fix_sym.replace(QStringLiteral("\t"), QStringLiteral(" "));
m_matching_results_table->setItem(
i, 0, create_item(QStringLiteral("%1").arg(iter.addr, 1, 16), iter.addr));
m_matching_results_table->setItem(
i, 1, create_item(QStringLiteral("%1").arg(iter.total_hits), iter.addr));
m_matching_results_table->setItem(i, 2,
create_item(QStringLiteral("%1").arg(iter.hits), iter.addr));
m_matching_results_table->setItem(i, 3,
create_item(QStringLiteral("%1").arg(fix_sym), iter.addr));
m_matching_results_table->setItem(i, 4, create_item(QStringLiteral(""), iter.addr));
i++;
}
// If we have ruled out all functions from being included.
if (m_include_active && m_include.empty())
{
m_matching_results_table->setRowCount(1);
m_matching_results_table->setItem(0, 3, create_item(tr("No possible functions left. Reset.")));
}
m_exclude_size_label->setText(tr("Excluded: %1").arg(m_exclude.size()));
m_include_size_label->setText(tr("Included: %1").arg(m_include.size()));
Core::System::GetInstance().GetJitInterface().ClearCache();
if (old_state == Core::State::Running)
Core::SetState(Core::State::Running);
}
void CodeDiffDialog::InfoDisp()
{
ModalMessageBox::information(
this, tr("Code Diff Tool Help"),
tr("Used to find functions based on when they should be running.\nSimilar to Cheat Engine "
"Ultimap.\n"
"A symbol map must be loaded prior to use.\n"
"Include/Exclude lists will persist on ending/restarting emulation.\nThese lists "
"will not persist on Dolphin close."
"\n\n'Start Recording': "
"keeps track of what functions run.\n'Stop Recording': erases current "
"recording without any change to the lists.\n'Code did not get executed': click while "
"recording, will add recorded functions to an exclude "
"list, then reset the recording list.\n'Code has been executed': click while recording, "
"will add recorded function to an include list, then reset the recording list.\n\nAfter "
"you use "
"both exclude and include once, the exclude list will be subtracted from the include "
"list "
"and any includes left over will be displayed.\nYou can continue to use "
"'Code did not get executed'/'Code has been executed' to narrow down the "
"results.\n\n"
"Saving will store the current list in Dolphin's Log folder (File -> Open User "
"Folder)"));
ModalMessageBox::information(
this, tr("Code Diff Tool Help"),
tr("Example:\n"
"You want to find a function that runs when HP is modified.\n1. Start recording and "
"play the game without letting HP be modified, then press 'Code did not get "
"executed'.\n2. Immediately gain/lose HP and press 'Code has been executed'.\n3. Repeat "
"1 or 2 to "
"narrow down the results.\nIncludes (Code has been executed) should "
"have short recordings focusing on what you want.\n\nPressing 'Code has been "
"executed' twice will only keep functions that ran for both recordings. Hits will update "
"to reflect the last recording's "
"number of Hits. Total Hits will reflect the total number of "
"times a function has been executed until the lists are cleared with Reset.\n\nRight "
"click -> 'Set blr' will place a "
"blr at the top of the symbol.\n"));
}
void CodeDiffDialog::OnContextMenu()
{
if (m_matching_results_table->currentItem() == nullptr)
return;
UpdateItem();
QMenu* menu = new QMenu(this);
menu->addAction(tr("&Go to start of function"), this, &CodeDiffDialog::OnGoTop);
menu->addAction(tr("Set &blr"), this, &CodeDiffDialog::OnSetBLR);
menu->addAction(tr("&Delete"), this, &CodeDiffDialog::OnDelete);
menu->exec(QCursor::pos());
}
void CodeDiffDialog::OnGoTop()
{
auto item = m_matching_results_table->currentItem();
if (!item)
return;
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(item->data(Qt::UserRole).toUInt());
if (!symbol)
return;
m_code_widget->SetAddress(symbol->address, CodeViewWidget::SetAddressUpdate::WithDetailedUpdate);
}
void CodeDiffDialog::OnDelete()
{
// Delete from include list and qtable widget
auto item = m_matching_results_table->currentItem();
if (!item)
return;
int row = m_matching_results_table->row(item);
if (row == -1)
return;
// TODO: If/when sorting is ever added, .erase needs to find item position instead; leaving as is
// for performance
if (!m_include.empty())
{
m_include.erase(m_include.begin() + row);
}
m_matching_results_table->removeRow(row);
}
void CodeDiffDialog::OnSetBLR()
{
auto item = m_matching_results_table->currentItem();
if (!item)
return;
Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(item->data(Qt::UserRole).toUInt());
if (!symbol)
return;
MarkRowBLR(item->row());
if (m_autosave_check->isChecked())
SaveDataBackup();
{
auto& system = Core::System::GetInstance();
Core::CPUThreadGuard guard(system);
system.GetPowerPC().GetDebugInterface().SetPatch(guard, symbol->address, 0x4E800020);
}
m_code_widget->Update();
}
void CodeDiffDialog::MarkRowBLR(int row)
{
m_matching_results_table->item(row, 0)->setForeground(QBrush(Qt::red));
m_matching_results_table->item(row, 1)->setForeground(QBrush(Qt::red));
m_matching_results_table->item(row, 2)->setForeground(QBrush(Qt::red));
m_matching_results_table->item(row, 3)->setForeground(QBrush(Qt::red));
m_matching_results_table->item(row, 4)->setForeground(QBrush(Qt::red));
m_matching_results_table->item(row, 4)->setText(QStringLiteral("X"));
}
void CodeDiffDialog::UpdateItem()
{
QTableWidgetItem* item = m_matching_results_table->currentItem();
if (!item)
return;
int row = m_matching_results_table->row(item);
if (row == -1)
return;
uint address = item->data(Qt::UserRole).toUInt();
auto symbolName = g_symbolDB.GetDescription(address);
if (symbolName == " --- ")
return;
QString newName =
QString::fromStdString(symbolName).replace(QStringLiteral("\t"), QStringLiteral(" "));
m_matching_results_table->item(row, 3)->setText(newName);
}
void CodeDiffDialog::UpdateButtons(bool running)
{
m_save_btn->setEnabled(running);
m_load_btn->setEnabled(running);
m_record_btn->setEnabled(running);
}

View file

@ -1,86 +0,0 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
class CodeWidget;
class QLabel;
class QPushButton;
class QCheckBox;
class QTableWidget;
struct Diff
{
u32 addr = 0;
std::string symbol;
u32 hits = 0;
u32 total_hits = 0;
bool operator<(const std::string& val) const { return symbol < val; }
};
class CodeDiffDialog : public QDialog
{
Q_OBJECT
public:
explicit CodeDiffDialog(CodeWidget* parent);
void reject() override;
private:
enum class UpdateType
{
Include,
Exclude,
Backup
};
void CreateWidgets();
void ConnectWidgets();
void SaveDataBackup();
void LoadDataBackup();
void ClearData();
void ClearBlockCache();
void OnClickItem();
void OnRecord(bool enabled);
std::vector<Diff> CalculateSymbolsFromProfile() const;
void OnInclude();
void OnExclude();
void RemoveMissingSymbolsFromIncludes(const std::vector<Diff>& symbol_diff);
void RemoveMatchingSymbolsFromIncludes(const std::vector<Diff>& symbol_list);
void Update(UpdateType type);
void InfoDisp();
void OnContextMenu();
void OnGoTop();
void OnDelete();
void OnSetBLR();
void MarkRowBLR(int row);
void UpdateItem();
void UpdateButtons(bool running);
QTableWidget* m_matching_results_table;
QCheckBox* m_autosave_check;
QLabel* m_exclude_size_label;
QLabel* m_include_size_label;
QPushButton* m_exclude_btn;
QPushButton* m_include_btn;
QPushButton* m_record_btn;
QPushButton* m_reset_btn;
QPushButton* m_save_btn;
QPushButton* m_load_btn;
QPushButton* m_help_btn;
CodeWidget* m_code_widget;
std::vector<Diff> m_exclude;
std::vector<Diff> m_include;
bool m_failed_requirements = false;
bool m_include_active = false;
};

View file

@ -27,6 +27,7 @@
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/BranchWatchDialog.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/Settings.h"
@ -35,7 +36,10 @@ static const QString BOX_SPLITTER_STYLESHEET = QStringLiteral(
"QSplitter::handle { border-top: 1px dashed black; width: 1px; margin-left: 10px; "
"margin-right: 10px; }");
CodeWidget::CodeWidget(QWidget* parent) : QDockWidget(parent), m_system(Core::System::GetInstance())
CodeWidget::CodeWidget(QWidget* parent)
: QDockWidget(parent), m_system(Core::System::GetInstance()),
m_branch_watch_dialog(
new BranchWatchDialog(m_system, m_system.GetPowerPC().GetBranchWatch(), this))
{
setWindowTitle(tr("Code"));
setObjectName(QStringLiteral("code"));
@ -105,7 +109,7 @@ void CodeWidget::CreateWidgets()
layout->setSpacing(0);
m_search_address = new QLineEdit;
m_code_diff = new QPushButton(tr("Diff"));
m_branch_watch = new QPushButton(tr("Branch Watch"));
m_code_view = new CodeViewWidget;
m_search_address->setPlaceholderText(tr("Search Address"));
@ -149,7 +153,7 @@ void CodeWidget::CreateWidgets()
m_code_splitter->addWidget(m_code_view);
layout->addWidget(m_search_address, 0, 0);
layout->addWidget(m_code_diff, 0, 2);
layout->addWidget(m_branch_watch, 0, 2);
layout->addWidget(m_code_splitter, 1, 0, -1, -1);
QWidget* widget = new QWidget(this);
@ -181,7 +185,7 @@ void CodeWidget::ConnectWidgets()
});
connect(m_search_callstack, &QLineEdit::textChanged, this, &CodeWidget::UpdateCallstack);
connect(m_code_diff, &QPushButton::pressed, this, &CodeWidget::OnDiff);
connect(m_branch_watch, &QPushButton::pressed, this, &CodeWidget::OnBranchWatchDialog);
connect(m_symbols_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectSymbol);
connect(m_callstack_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectCallstack);
@ -209,15 +213,11 @@ void CodeWidget::ConnectWidgets()
connect(m_code_view, &CodeViewWidget::ShowMemory, this, &CodeWidget::ShowMemory);
}
void CodeWidget::OnDiff()
void CodeWidget::OnBranchWatchDialog()
{
if (!m_diff_dialog)
m_diff_dialog = new CodeDiffDialog(this);
m_diff_dialog->setWindowFlag(Qt::WindowMinimizeButtonHint);
SetQWidgetWindowDecorations(m_diff_dialog);
m_diff_dialog->show();
m_diff_dialog->raise();
m_diff_dialog->activateWindow();
m_branch_watch_dialog->open();
m_branch_watch_dialog->raise();
m_branch_watch_dialog->activateWindow();
}
void CodeWidget::OnSearchAddress()
@ -359,7 +359,7 @@ void CodeWidget::UpdateCallstack()
{
const QString name = QString::fromStdString(frame.Name.substr(0, frame.Name.length() - 1));
if (name.toUpper().indexOf(filter.toUpper()) == -1)
if (!name.contains(filter, Qt::CaseInsensitive))
continue;
auto* item = new QListWidgetItem(name);
@ -389,11 +389,15 @@ void CodeWidget::UpdateSymbols()
item->setData(Qt::UserRole, symbol.second.address);
if (name.toUpper().indexOf(m_symbol_filter.toUpper()) != -1)
if (name.contains(m_symbol_filter, Qt::CaseInsensitive))
m_symbols_list->addItem(item);
}
m_symbols_list->sortItems();
// TODO: There seems to be a lack of a ubiquitous signal for when symbols change.
// This is the best location to catch the signals from MenuBar and CodeViewWidget.
m_branch_watch_dialog->UpdateSymbols();
}
void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol)
@ -411,7 +415,7 @@ void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol)
const QString name =
QString::fromStdString(fmt::format("> {} ({:08x})", call_symbol->name, addr));
if (name.toUpper().indexOf(filter.toUpper()) == -1)
if (!name.contains(filter, Qt::CaseInsensitive))
continue;
auto* item = new QListWidgetItem(name);
@ -436,7 +440,7 @@ void CodeWidget::UpdateFunctionCallers(const Common::Symbol* symbol)
const QString name =
QString::fromStdString(fmt::format("< {} ({:08x})", caller_symbol->name, addr));
if (name.toUpper().indexOf(filter.toUpper()) == -1)
if (!name.contains(filter, Qt::CaseInsensitive))
continue;
auto* item = new QListWidgetItem(name);
@ -464,6 +468,9 @@ void CodeWidget::Step()
power_pc.SetMode(old_mode);
Core::DisplayMessage(tr("Step successful!").toStdString(), 2000);
// Will get a UpdateDisasmDialog(), don't update the GUI here.
// TODO: Step doesn't cause EmulationStateChanged to be emitted, so it has to call this manually.
m_branch_watch_dialog->Update();
}
void CodeWidget::StepOver()

View file

@ -7,9 +7,9 @@
#include <QString>
#include "Common/CommonTypes.h"
#include "DolphinQt/Debugger/CodeDiffDialog.h"
#include "DolphinQt/Debugger/CodeViewWidget.h"
class BranchWatchDialog;
class QCloseEvent;
class QLineEdit;
class QShowEvent;
@ -41,7 +41,7 @@ public:
void ShowPC();
void SetPC();
void OnDiff();
void OnBranchWatchDialog();
void ToggleBreakpoint();
void AddBreakpoint();
void SetAddress(u32 address, CodeViewWidget::SetAddressUpdate update);
@ -72,9 +72,9 @@ private:
Core::System& m_system;
CodeDiffDialog* m_diff_dialog = nullptr;
BranchWatchDialog* m_branch_watch_dialog;
QLineEdit* m_search_address;
QPushButton* m_code_diff;
QPushButton* m_branch_watch;
QLineEdit* m_search_callstack;
QListWidget* m_callstack_list;

View file

@ -252,7 +252,7 @@ void NetworkWidget::Update()
// needed because there's a race condition on the IOS instance otherwise
Core::CPUThreadGuard guard(Core::System::GetInstance());
auto* ios = IOS::HLE::GetIOS();
auto* ios = guard.GetSystem().GetIOS();
if (!ios)
return;

View file

@ -144,9 +144,10 @@
<ClCompile Include="Debugger\AssembleInstructionDialog.cpp" />
<ClCompile Include="Debugger\AssemblerWidget.cpp" />
<ClCompile Include="Debugger\AssemblyEditor.cpp" />
<ClCompile Include="Debugger\BranchWatchDialog.cpp" />
<ClCompile Include="Debugger\BranchWatchTableModel.cpp" />
<ClCompile Include="Debugger\BreakpointDialog.cpp" />
<ClCompile Include="Debugger\BreakpointWidget.cpp" />
<ClCompile Include="Debugger\CodeDiffDialog.cpp" />
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
<ClCompile Include="Debugger\CodeWidget.cpp" />
<ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
@ -356,9 +357,10 @@
<QtMoc Include="Debugger\AssembleInstructionDialog.h" />
<QtMoc Include="Debugger\AssemblerWidget.h" />
<QtMoc Include="Debugger\AssemblyEditor.h" />
<QtMoc Include="Debugger\BranchWatchDialog.h" />
<QtMoc Include="Debugger\BranchWatchTableModel.h" />
<QtMoc Include="Debugger\BreakpointDialog.h" />
<QtMoc Include="Debugger\BreakpointWidget.h" />
<QtMoc Include="Debugger\CodeDiffDialog.h" />
<QtMoc Include="Debugger\CodeViewWidget.h" />
<QtMoc Include="Debugger\CodeWidget.h" />
<QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />

View file

@ -46,7 +46,6 @@
#include "Common/FileUtil.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/DVD/DVDInterface.h"
#include "Core/HW/EXI/EXI.h"
@ -437,7 +436,7 @@ void GameList::ShowContextMenu(const QPoint&)
// system menu, trigger a refresh.
Settings::Instance().NANDRefresh();
});
perform_disc_update->setEnabled(!Core::IsRunning() || !SConfig::GetInstance().bWii);
perform_disc_update->setEnabled(!Core::IsRunning() || !Core::System::GetInstance().IsWii());
}
if (!is_mod_descriptor && platform == DiscIO::Platform::WiiWAD)

View file

@ -23,7 +23,6 @@
#include "Core/Config/GraphicsSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/UISettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/FreeLookManager.h"
#include "Core/Host.h"
@ -266,7 +265,7 @@ void HotkeyScheduler::Run()
// TODO: HK_MBP_ADD
if (SConfig::GetInstance().bWii)
if (Core::System::GetInstance().IsWii())
{
int wiimote_id = -1;
if (IsHotkey(HK_WIIMOTE1_CONNECT))
@ -411,6 +410,12 @@ void HotkeyScheduler::Run()
case AspectMode::Custom:
OSD::AddMessage("Custom");
break;
case AspectMode::CustomStretch:
OSD::AddMessage("Custom (Stretch)");
break;
case AspectMode::Raw:
OSD::AddMessage("Raw (Square Pixels)");
break;
case AspectMode::Auto:
default:
OSD::AddMessage("Auto");

View file

@ -45,8 +45,8 @@
#include "Core/Config/AchievementSettings.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/Config/UISettings.h"
#include "Core/Config/WiimoteSettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/FreeLookManager.h"
#include "Core/HW/DVD/DVDInterface.h"
@ -1511,18 +1511,24 @@ void MainWindow::ShowInfinityBase()
void MainWindow::StateLoad()
{
QString path =
DolphinFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
tr("All Save States (*.sav *.s##);; All Files (*)"));
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?
QDir::currentPath() :
QString::fromStdString(Config::Get(Config::MAIN_CURRENT_STATE_PATH));
QString path = DolphinFileDialog::getOpenFileName(
this, tr("Select a File"), dialog_path, tr("All Save States (*.sav *.s##);; All Files (*)"));
Config::SetBase(Config::MAIN_CURRENT_STATE_PATH, QFileInfo(path).dir().path().toStdString());
if (!path.isEmpty())
State::LoadAs(path.toStdString());
}
void MainWindow::StateSave()
{
QString path =
DolphinFileDialog::getSaveFileName(this, tr("Select a File"), QDir::currentPath(),
tr("All Save States (*.sav *.s##);; All Files (*)"));
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?
QDir::currentPath() :
QString::fromStdString(Config::Get(Config::MAIN_CURRENT_STATE_PATH));
QString path = DolphinFileDialog::getSaveFileName(
this, tr("Select a File"), dialog_path, tr("All Save States (*.sav *.s##);; All Files (*)"));
Config::SetBase(Config::MAIN_CURRENT_STATE_PATH, QFileInfo(path).dir().path().toStdString());
if (!path.isEmpty())
State::SaveAs(path.toStdString());
}
@ -2077,7 +2083,7 @@ void MainWindow::ShowTASInput()
for (int i = 0; i < num_wii_controllers; i++)
{
if (Config::Get(Config::GetInfoForWiimoteSource(i)) == WiimoteSource::Emulated &&
(!Core::IsRunning() || SConfig::GetInstance().bWii))
(!Core::IsRunning() || Core::System::GetInstance().IsWii()))
{
SetQWidgetWindowDecorations(m_wii_tas_input_windows[i]);
m_wii_tas_input_windows[i]->show();

View file

@ -173,7 +173,6 @@ void MenuBar::OnDebugModeToggled(bool enabled)
{
// Options
m_boot_to_pause->setVisible(enabled);
m_automatic_start->setVisible(enabled);
m_reset_ignore_panic_handler->setVisible(enabled);
m_change_font->setVisible(enabled);
@ -569,13 +568,6 @@ void MenuBar::AddOptionsMenu()
connect(m_boot_to_pause, &QAction::toggled, this,
[](bool enable) { SConfig::GetInstance().bBootToPause = enable; });
m_automatic_start = options_menu->addAction(tr("&Automatic Start"));
m_automatic_start->setCheckable(true);
m_automatic_start->setChecked(SConfig::GetInstance().bAutomaticStart);
connect(m_automatic_start, &QAction::toggled, this,
[](bool enable) { SConfig::GetInstance().bAutomaticStart = enable; });
m_reset_ignore_panic_handler = options_menu->addAction(tr("Reset Ignore Panic Handler"));
connect(m_reset_ignore_panic_handler, &QAction::triggered, this, []() {

View file

@ -241,7 +241,6 @@ private:
// Options
QAction* m_boot_to_pause;
QAction* m_automatic_start;
QAction* m_reset_ignore_panic_handler;
QAction* m_change_font;
QAction* m_controllers_action;

View file

@ -262,7 +262,16 @@ void RenderWidget::SetCursorLocked(bool locked, bool follow_aspect_ratio)
if (ClipCursor(&rect))
#else
// TODO: implement on other platforms. Probably XGrabPointer on Linux.
// TODO: Implement on other platforms. XGrabPointer on Linux X11 should be equivalent to
// ClipCursor on Windows, though XFixesCreatePointerBarrier and XFixesDestroyPointerBarrier
// may also work. On Wayland zwp_pointer_constraints_v1::confine_pointer and
// zwp_pointer_constraints_v1::destroy provide this functionality.
// More info:
// https://stackoverflow.com/a/36269507
// https://tronche.com/gui/x/xlib/input/XGrabPointer.html
// https://www.x.org/releases/X11R7.7/doc/fixesproto/fixesproto.txt
// https://wayland.app/protocols/pointer-constraints-unstable-v1
// The setting is hidden in the UI if not implemented
if (false)
#endif