Merge pull request #12799 from mitaclaw/branch-watch-tool-fixes-3
Branch Watch Tool: Add Set Breakpoints Submenu
This commit is contained in:
commit
69fc754c65
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <ranges>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
@ -46,6 +47,7 @@
|
||||||
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
||||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||||
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
|
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
|
||||||
|
#include "DolphinQt/Resources.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
class BranchWatchProxyModel final : public QSortFilterProxyModel
|
class BranchWatchProxyModel final : public QSortFilterProxyModel
|
||||||
|
@ -99,7 +101,6 @@ public:
|
||||||
this->*member = std::nullopt;
|
this->*member = std::nullopt;
|
||||||
invalidateRowsFilter();
|
invalidateRowsFilter();
|
||||||
}
|
}
|
||||||
void OnDelete(QModelIndexList index_list);
|
|
||||||
|
|
||||||
bool IsBranchTypeAllowed(UGeckoInstruction inst) const;
|
bool IsBranchTypeAllowed(UGeckoInstruction inst) const;
|
||||||
void SetInspected(const QModelIndex& index);
|
void SetInspected(const QModelIndex& index);
|
||||||
|
@ -155,13 +156,6 @@ bool BranchWatchProxyModel::filterAcceptsRow(int source_row, const QModelIndex&)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchProxyModel::OnDelete(QModelIndexList index_list)
|
|
||||||
{
|
|
||||||
std::transform(index_list.begin(), index_list.end(), index_list.begin(),
|
|
||||||
[this](const QModelIndex& index) { return mapToSource(index); });
|
|
||||||
sourceModel()->OnDelete(std::move(index_list));
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr bool BranchSavesLR(UGeckoInstruction inst)
|
static constexpr bool BranchSavesLR(UGeckoInstruction inst)
|
||||||
{
|
{
|
||||||
DEBUG_ASSERT(inst.OPCD == 18 || inst.OPCD == 16 ||
|
DEBUG_ASSERT(inst.OPCD == 18 || inst.OPCD == 16 ||
|
||||||
|
@ -497,9 +491,13 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
|
||||||
return group_box;
|
return group_box;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
UpdateIcons();
|
||||||
|
|
||||||
connect(m_timer = new QTimer, &QTimer::timeout, this, &BranchWatchDialog::OnTimeout);
|
connect(m_timer = new QTimer, &QTimer::timeout, this, &BranchWatchDialog::OnTimeout);
|
||||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||||
&BranchWatchDialog::OnEmulationStateChanged);
|
&BranchWatchDialog::OnEmulationStateChanged);
|
||||||
|
connect(&Settings::Instance(), &Settings::ThemeChanged, this,
|
||||||
|
&BranchWatchDialog::OnThemeChanged);
|
||||||
connect(m_table_proxy, &BranchWatchProxyModel::layoutChanged, this,
|
connect(m_table_proxy, &BranchWatchProxyModel::layoutChanged, this,
|
||||||
&BranchWatchDialog::UpdateStatus);
|
&BranchWatchDialog::UpdateStatus);
|
||||||
|
|
||||||
|
@ -701,13 +699,19 @@ void BranchWatchDialog::OnEmulationStateChanged(Core::State new_state)
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::OnThemeChanged()
|
||||||
|
{
|
||||||
|
UpdateIcons();
|
||||||
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnHelp()
|
void BranchWatchDialog::OnHelp()
|
||||||
{
|
{
|
||||||
ModalMessageBox::information(
|
ModalMessageBox::information(
|
||||||
this, tr("Branch Watch Tool Help (1/4)"),
|
this, tr("Branch Watch Tool Help (1/4)"),
|
||||||
tr("Branch Watch is a code-searching tool that can isolate branches tracked by the emulated "
|
tr("Branch Watch is a code-searching tool that can isolate branches tracked by the emulated "
|
||||||
"CPU by testing candidate branches with simple criteria. If you are familiar with Cheat "
|
"CPU by testing candidate branches with simple criteria. If you are familiar with Cheat "
|
||||||
"Engine's Ultimap, Branch Watch is similar to that.\n\n"
|
"Engine's Ultimap, Branch Watch is similar to that."
|
||||||
|
"\n\n"
|
||||||
"Press the \"Start Branch Watch\" button to activate Branch Watch. Branch Watch persists "
|
"Press the \"Start Branch Watch\" button to activate Branch Watch. Branch Watch persists "
|
||||||
"across emulation sessions, and a snapshot of your progress can be saved to and loaded "
|
"across emulation sessions, and a snapshot of your progress can be saved to and loaded "
|
||||||
"from the User Directory to persist after Dolphin Emulator is closed. \"Save As...\" and "
|
"from the User Directory to persist after Dolphin Emulator is closed. \"Save As...\" and "
|
||||||
|
@ -730,7 +734,8 @@ void BranchWatchDialog::OnHelp()
|
||||||
"taken since the last time it was checked. It is also possible to reduce the candidates "
|
"taken since the last time it was checked. It is also possible to reduce the candidates "
|
||||||
"by determining whether a branch instruction has or has not been overwritten since it was "
|
"by determining whether a branch instruction has or has not been overwritten since it was "
|
||||||
"first hit. Filter the candidates by branch kind, branch condition, origin or destination "
|
"first hit. Filter the candidates by branch kind, branch condition, origin or destination "
|
||||||
"address, and origin or destination symbol name.\n\n"
|
"address, and origin or destination symbol name."
|
||||||
|
"\n\n"
|
||||||
"After enough passes and experimentation, you may be able to find function calls and "
|
"After enough passes and experimentation, you may be able to find function calls and "
|
||||||
"conditional code paths that are only taken when an action is performed in the emulated "
|
"conditional code paths that are only taken when an action is performed in the emulated "
|
||||||
"software."));
|
"software."));
|
||||||
|
@ -738,17 +743,27 @@ void BranchWatchDialog::OnHelp()
|
||||||
this, tr("Branch Watch Tool Help (4/4)"),
|
this, tr("Branch Watch Tool Help (4/4)"),
|
||||||
tr("Rows in the table can be left-clicked on the origin, destination, and symbol columns to "
|
tr("Rows in the table can be left-clicked on the origin, destination, and symbol columns to "
|
||||||
"view the associated address in Code View. Right-clicking the selected row(s) will bring "
|
"view the associated address in Code View. Right-clicking the selected row(s) will bring "
|
||||||
"up a context menu.\n\n"
|
"up a context menu."
|
||||||
|
"\n\n"
|
||||||
|
"If the origin, destination, or symbol columns are right-clicked, an action copy the "
|
||||||
|
"relevant address(es) to the clipboard will be available, and an action to set a "
|
||||||
|
"breakpoint at the relevant address(es) will be available. Note that, for the origin / "
|
||||||
|
"destination symbol columns, these actions will only be enabled if every row in the "
|
||||||
|
"selection has a symbol."
|
||||||
|
"\n\n"
|
||||||
"If the origin column of a row selection is right-clicked, an action to replace the "
|
"If the origin column of a row selection is right-clicked, an action to replace the "
|
||||||
"branch instruction at the origin(s) with a NOP instruction (No Operation), and an action "
|
"branch instruction at the origin(s) with a NOP instruction (No Operation) will be "
|
||||||
"to copy the address(es) to the clipboard will be available.\n\n"
|
"available."
|
||||||
|
"\n\n"
|
||||||
"If the destination column of a row selection is right-clicked, an action to replace the "
|
"If the destination column of a row selection is right-clicked, an action to replace the "
|
||||||
"instruction at the destination(s) with a BLR instruction (Branch to Link Register) will "
|
"instruction at the destination(s) with a BLR instruction (Branch to Link Register) will "
|
||||||
"be available, but only if the branch instruction at every origin saves the link "
|
"be available, but will only be enabled if the branch instruction at every origin updates "
|
||||||
"register, and an action to copy the address(es) to the clipboard will be available.\n\n"
|
"the link register."
|
||||||
|
"\n\n"
|
||||||
"If the origin / destination symbol column of a row selection is right-clicked, an action "
|
"If the origin / destination symbol column of a row selection is right-clicked, an action "
|
||||||
"to replace the instruction(s) at the start of the symbol with a BLR instruction will be "
|
"to replace the instruction at the start of the symbol(s) with a BLR instruction will be "
|
||||||
"available, but only if every origin / destination symbol is found.\n\n"
|
"available, but will only be enabled if every row in the selection has a symbol."
|
||||||
|
"\n\n"
|
||||||
"All context menus have the action to delete the selected row(s) from the candidates."));
|
"All context menus have the action to delete the selected row(s) from the candidates."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,68 +816,18 @@ void BranchWatchDialog::OnTableClicked(const QModelIndex& index)
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableContextMenu(const QPoint& pos)
|
void BranchWatchDialog::OnTableContextMenu(const QPoint& pos)
|
||||||
{
|
{
|
||||||
|
if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns)
|
||||||
|
{
|
||||||
|
m_mnu_column_visibility->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const QModelIndex index = m_table_view->indexAt(pos);
|
const QModelIndex index = m_table_view->indexAt(pos);
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return;
|
return;
|
||||||
QModelIndexList index_list = m_table_view->selectionModel()->selectedRows(index.column());
|
m_index_list_temp = m_table_view->selectionModel()->selectedRows(index.column());
|
||||||
|
GetTableContextMenu(index)->exec(m_table_view->viewport()->mapToGlobal(pos));
|
||||||
QMenu* const menu = new QMenu;
|
m_index_list_temp.clear();
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose, true);
|
m_index_list_temp.shrink_to_fit();
|
||||||
|
|
||||||
menu->addAction(tr("&Delete"), [this, index_list]() { OnTableDelete(std::move(index_list)); });
|
|
||||||
switch (index.column())
|
|
||||||
{
|
|
||||||
case Column::Origin:
|
|
||||||
{
|
|
||||||
QAction* const action = menu->addAction(tr("Insert &NOP"));
|
|
||||||
if (Core::GetState(m_system) != Core::State::Uninitialized)
|
|
||||||
connect(action, &QAction::triggered,
|
|
||||||
[this, index_list]() { OnTableSetNOP(std::move(index_list)); });
|
|
||||||
else
|
|
||||||
action->setEnabled(false);
|
|
||||||
menu->addAction(tr("&Copy Address"), [this, index_list = std::move(index_list)]() {
|
|
||||||
OnTableCopyAddress(std::move(index_list));
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Column::Destination:
|
|
||||||
{
|
|
||||||
QAction* const action = menu->addAction(tr("Insert &BLR"));
|
|
||||||
const bool enable_action =
|
|
||||||
Core::GetState(m_system) != Core::State::Uninitialized &&
|
|
||||||
std::all_of(index_list.begin(), index_list.end(), [this](const QModelIndex& idx) {
|
|
||||||
const QModelIndex sibling = idx.siblingAtColumn(Column::Instruction);
|
|
||||||
return BranchSavesLR(m_table_proxy->data(sibling, UserRole::ClickRole).value<u32>());
|
|
||||||
});
|
|
||||||
if (enable_action)
|
|
||||||
connect(action, &QAction::triggered,
|
|
||||||
[this, index_list]() { OnTableSetBLR(std::move(index_list)); });
|
|
||||||
else
|
|
||||||
action->setEnabled(false);
|
|
||||||
menu->addAction(tr("&Copy Address"), [this, index_list = std::move(index_list)]() {
|
|
||||||
OnTableCopyAddress(std::move(index_list));
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Column::OriginSymbol:
|
|
||||||
case Column::DestinSymbol:
|
|
||||||
{
|
|
||||||
QAction* const action = menu->addAction(tr("Insert &BLR at start"));
|
|
||||||
const bool enable_action =
|
|
||||||
Core::GetState(m_system) != Core::State::Uninitialized &&
|
|
||||||
std::all_of(index_list.begin(), index_list.end(), [this](const QModelIndex& idx) {
|
|
||||||
return m_table_proxy->data(idx, UserRole::ClickRole).isValid();
|
|
||||||
});
|
|
||||||
if (enable_action)
|
|
||||||
connect(action, &QAction::triggered, [this, index_list = std::move(index_list)]() {
|
|
||||||
OnTableSetBLR(std::move(index_list));
|
|
||||||
});
|
|
||||||
else
|
|
||||||
action->setEnabled(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
menu->exec(m_table_view->viewport()->mapToGlobal(pos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableHeaderContextMenu(const QPoint& pos)
|
void BranchWatchDialog::OnTableHeaderContextMenu(const QPoint& pos)
|
||||||
|
@ -870,67 +835,72 @@ void BranchWatchDialog::OnTableHeaderContextMenu(const QPoint& pos)
|
||||||
m_mnu_column_visibility->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
|
m_mnu_column_visibility->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableDelete(QModelIndexList index_list)
|
void BranchWatchDialog::OnTableDelete()
|
||||||
{
|
{
|
||||||
m_table_proxy->OnDelete(std::move(index_list));
|
std::ranges::transform(
|
||||||
|
m_index_list_temp, m_index_list_temp.begin(),
|
||||||
|
[this](const QModelIndex& index) { return m_table_proxy->mapToSource(index); });
|
||||||
|
std::ranges::sort(m_index_list_temp, std::less{});
|
||||||
|
for (const auto& index : std::ranges::reverse_view{m_index_list_temp})
|
||||||
|
{
|
||||||
|
if (!index.isValid())
|
||||||
|
continue;
|
||||||
|
m_table_model->removeRow(index.row());
|
||||||
|
}
|
||||||
UpdateStatus();
|
UpdateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableDeleteKeypress()
|
void BranchWatchDialog::OnTableDeleteKeypress()
|
||||||
{
|
{
|
||||||
OnTableDelete(m_table_view->selectionModel()->selectedRows());
|
m_index_list_temp = m_table_view->selectionModel()->selectedRows();
|
||||||
|
OnTableDelete();
|
||||||
|
m_index_list_temp.clear();
|
||||||
|
m_index_list_temp.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableSetBLR(QModelIndexList index_list)
|
void BranchWatchDialog::OnTableSetBLR()
|
||||||
{
|
{
|
||||||
for (const QModelIndex& index : index_list)
|
SetStubPatches(0x4e800020);
|
||||||
{
|
|
||||||
m_system.GetPowerPC().GetDebugInterface().SetPatch(
|
|
||||||
Core::CPUThreadGuard{m_system},
|
|
||||||
m_table_proxy->data(index, UserRole::ClickRole).value<u32>(), 0x4e800020);
|
|
||||||
m_table_proxy->SetInspected(index);
|
|
||||||
}
|
|
||||||
// TODO: This is not ideal. What I need is a signal for when memory has been changed by the GUI,
|
|
||||||
// but I cannot find one. UpdateDisasmDialog comes close, but does too much in one signal. For
|
|
||||||
// example, CodeViewWidget will scroll to the current PC when UpdateDisasmDialog is signaled. This
|
|
||||||
// seems like a pervasive issue. For example, modifying an instruction in the CodeViewWidget will
|
|
||||||
// not reflect in the MemoryViewWidget, and vice versa. Neither of these widgets changing memory
|
|
||||||
// will reflect in the JITWidget, either. At the very least, we can make sure the CodeWidget
|
|
||||||
// is updated in an acceptable way.
|
|
||||||
m_code_widget->Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableSetNOP(QModelIndexList index_list)
|
void BranchWatchDialog::OnTableSetNOP()
|
||||||
{
|
{
|
||||||
for (const QModelIndex& index : index_list)
|
SetStubPatches(0x60000000);
|
||||||
{
|
|
||||||
m_system.GetPowerPC().GetDebugInterface().SetPatch(
|
|
||||||
Core::CPUThreadGuard{m_system},
|
|
||||||
m_table_proxy->data(index, UserRole::ClickRole).value<u32>(), 0x60000000);
|
|
||||||
m_table_proxy->SetInspected(index);
|
|
||||||
}
|
|
||||||
// Same issue as OnSetBLR.
|
|
||||||
m_code_widget->Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::OnTableCopyAddress(QModelIndexList index_list)
|
void BranchWatchDialog::OnTableCopyAddress()
|
||||||
{
|
{
|
||||||
auto iter = index_list.begin();
|
auto iter = m_index_list_temp.begin();
|
||||||
if (iter == index_list.end())
|
if (iter == m_index_list_temp.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString text;
|
QString text;
|
||||||
text.reserve(index_list.size() * 9 - 1);
|
text.reserve(m_index_list_temp.size() * 9 - 1);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
text.append(QString::number(m_table_proxy->data(*iter, UserRole::ClickRole).value<u32>(), 16));
|
text.append(QString::number(m_table_proxy->data(*iter, UserRole::ClickRole).value<u32>(), 16));
|
||||||
if (++iter == index_list.end())
|
if (++iter == m_index_list_temp.end())
|
||||||
break;
|
break;
|
||||||
text.append(QChar::fromLatin1('\n'));
|
text.append(QChar::fromLatin1('\n'));
|
||||||
}
|
}
|
||||||
QApplication::clipboard()->setText(text);
|
QApplication::clipboard()->setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::OnTableSetBreakpointBreak()
|
||||||
|
{
|
||||||
|
SetBreakpoints(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::OnTableSetBreakpointLog()
|
||||||
|
{
|
||||||
|
SetBreakpoints(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::OnTableSetBreakpointBoth()
|
||||||
|
{
|
||||||
|
SetBreakpoints(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::SaveSettings()
|
void BranchWatchDialog::SaveSettings()
|
||||||
{
|
{
|
||||||
auto& settings = Settings::GetQSettings();
|
auto& settings = Settings::GetQSettings();
|
||||||
|
@ -983,6 +953,12 @@ void BranchWatchDialog::UpdateStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::UpdateIcons()
|
||||||
|
{
|
||||||
|
m_icn_full = Resources::GetThemeIcon("debugger_breakpoint");
|
||||||
|
m_icn_partial = Resources::GetThemeIcon("stop");
|
||||||
|
}
|
||||||
|
|
||||||
void BranchWatchDialog::Save(const Core::CPUThreadGuard& guard, const std::string& filepath)
|
void BranchWatchDialog::Save(const Core::CPUThreadGuard& guard, const std::string& filepath)
|
||||||
{
|
{
|
||||||
File::IOFile file(filepath, "w");
|
File::IOFile file(filepath, "w");
|
||||||
|
@ -1019,3 +995,146 @@ void BranchWatchDialog::AutoSave(const Core::CPUThreadGuard& guard)
|
||||||
return;
|
return;
|
||||||
Save(guard, m_autosave_filepath ? m_autosave_filepath.value() : GetSnapshotDefaultFilepath());
|
Save(guard, m_autosave_filepath ? m_autosave_filepath.value() : GetSnapshotDefaultFilepath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::SetStubPatches(u32 value) const
|
||||||
|
{
|
||||||
|
auto& debug_interface = m_system.GetPowerPC().GetDebugInterface();
|
||||||
|
for (const Core::CPUThreadGuard guard(m_system); const QModelIndex& index : m_index_list_temp)
|
||||||
|
{
|
||||||
|
debug_interface.SetPatch(guard, m_table_proxy->data(index, UserRole::ClickRole).value<u32>(),
|
||||||
|
value);
|
||||||
|
m_table_proxy->SetInspected(index);
|
||||||
|
}
|
||||||
|
// TODO: This is not ideal. What I need is a signal for when memory has been changed by the GUI,
|
||||||
|
// but I cannot find one. UpdateDisasmDialog comes close, but does too much in one signal. For
|
||||||
|
// example, CodeViewWidget will scroll to the current PC when UpdateDisasmDialog is signaled. This
|
||||||
|
// seems like a pervasive issue. For example, modifying an instruction in the CodeViewWidget will
|
||||||
|
// not reflect in the MemoryViewWidget, and vice versa. Neither of these widgets changing memory
|
||||||
|
// will reflect in the JITWidget, either. At the very least, we can make sure the CodeWidget
|
||||||
|
// is updated in an acceptable way.
|
||||||
|
m_code_widget->Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BranchWatchDialog::SetBreakpoints(bool break_on_hit, bool log_on_hit) const
|
||||||
|
{
|
||||||
|
auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
|
||||||
|
for (const QModelIndex& index : m_index_list_temp)
|
||||||
|
{
|
||||||
|
const u32 address = m_table_proxy->data(index, UserRole::ClickRole).value<u32>();
|
||||||
|
breakpoints.Add(address, false, break_on_hit, log_on_hit, {});
|
||||||
|
}
|
||||||
|
emit m_code_widget->BreakpointsChanged();
|
||||||
|
m_code_widget->Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu* BranchWatchDialog::GetTableContextMenu(const QModelIndex& index)
|
||||||
|
{
|
||||||
|
if (m_mnu_table_context == nullptr)
|
||||||
|
{
|
||||||
|
m_mnu_table_context = new QMenu(this);
|
||||||
|
m_mnu_table_context->addAction(tr("&Delete"), this, &BranchWatchDialog::OnTableDelete);
|
||||||
|
m_act_insert_nop =
|
||||||
|
m_mnu_table_context->addAction(tr("Insert &NOP"), this, &BranchWatchDialog::OnTableSetNOP);
|
||||||
|
m_act_insert_blr =
|
||||||
|
m_mnu_table_context->addAction(tr("Insert &BLR"), this, &BranchWatchDialog::OnTableSetBLR);
|
||||||
|
m_act_copy_address = m_mnu_table_context->addAction(tr("&Copy Address"), this,
|
||||||
|
&BranchWatchDialog::OnTableCopyAddress);
|
||||||
|
|
||||||
|
m_mnu_set_breakpoint = new QMenu(tr("Set Brea&kpoint"));
|
||||||
|
m_act_break_on_hit = m_mnu_set_breakpoint->addAction(
|
||||||
|
tr("&Break On Hit"), this, &BranchWatchDialog::OnTableSetBreakpointBreak);
|
||||||
|
m_act_log_on_hit = m_mnu_set_breakpoint->addAction(tr("&Log On Hit"), this,
|
||||||
|
&BranchWatchDialog::OnTableSetBreakpointLog);
|
||||||
|
m_act_both_on_hit = m_mnu_set_breakpoint->addAction(
|
||||||
|
tr("Break &And Log On Hit"), this, &BranchWatchDialog::OnTableSetBreakpointBoth);
|
||||||
|
m_mnu_table_context->addMenu(m_mnu_set_breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool core_initialized = Core::GetState(m_system) != Core::State::Uninitialized;
|
||||||
|
|
||||||
|
bool supported_column = true;
|
||||||
|
switch (index.column())
|
||||||
|
{
|
||||||
|
case Column::Origin:
|
||||||
|
m_act_insert_blr->setVisible(false);
|
||||||
|
m_act_insert_nop->setVisible(true);
|
||||||
|
m_act_insert_nop->setEnabled(core_initialized);
|
||||||
|
m_act_copy_address->setEnabled(true);
|
||||||
|
m_mnu_set_breakpoint->setEnabled(true);
|
||||||
|
break;
|
||||||
|
case Column::Destination:
|
||||||
|
{
|
||||||
|
m_act_insert_nop->setVisible(false);
|
||||||
|
m_act_insert_blr->setVisible(true);
|
||||||
|
const bool all_branches_save_lr =
|
||||||
|
core_initialized &&
|
||||||
|
std::all_of(
|
||||||
|
m_index_list_temp.begin(), m_index_list_temp.end(), [this](const QModelIndex& idx) {
|
||||||
|
const QModelIndex sibling = idx.siblingAtColumn(Column::Instruction);
|
||||||
|
return BranchSavesLR(m_table_proxy->data(sibling, UserRole::ClickRole).value<u32>());
|
||||||
|
});
|
||||||
|
m_act_insert_blr->setEnabled(all_branches_save_lr);
|
||||||
|
m_act_copy_address->setEnabled(true);
|
||||||
|
m_mnu_set_breakpoint->setEnabled(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Column::OriginSymbol:
|
||||||
|
case Column::DestinSymbol:
|
||||||
|
{
|
||||||
|
m_act_insert_nop->setVisible(false);
|
||||||
|
m_act_insert_blr->setVisible(true);
|
||||||
|
const bool all_symbols_valid =
|
||||||
|
core_initialized &&
|
||||||
|
std::all_of(m_index_list_temp.begin(), m_index_list_temp.end(),
|
||||||
|
[this](const QModelIndex& idx) {
|
||||||
|
return m_table_proxy->data(idx, UserRole::ClickRole).isValid();
|
||||||
|
});
|
||||||
|
m_act_insert_blr->setEnabled(all_symbols_valid);
|
||||||
|
m_act_copy_address->setEnabled(all_symbols_valid);
|
||||||
|
m_mnu_set_breakpoint->setEnabled(all_symbols_valid);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
m_act_insert_nop->setVisible(false);
|
||||||
|
m_act_insert_blr->setVisible(false);
|
||||||
|
supported_column = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_act_copy_address->setVisible(supported_column);
|
||||||
|
m_mnu_set_breakpoint->menuAction()->setVisible(supported_column);
|
||||||
|
// Setting breakpoints while nothing is being emulated is discouraged in the UI.
|
||||||
|
m_mnu_set_breakpoint->setEnabled(core_initialized);
|
||||||
|
|
||||||
|
if (core_initialized && supported_column)
|
||||||
|
{
|
||||||
|
qsizetype bp_break_count = 0, bp_log_count = 0, bp_both_count = 0;
|
||||||
|
for (auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
|
||||||
|
const QModelIndex& idx : m_index_list_temp)
|
||||||
|
{
|
||||||
|
if (const TBreakPoint* bp =
|
||||||
|
breakpoints.GetBreakpoint(m_table_proxy->data(idx, UserRole::ClickRole).value<u32>()))
|
||||||
|
{
|
||||||
|
if (bp->is_temporary)
|
||||||
|
continue;
|
||||||
|
if (bp->break_on_hit && bp->log_on_hit)
|
||||||
|
{
|
||||||
|
bp_both_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bp_break_count += bp->break_on_hit;
|
||||||
|
bp_log_count += bp->log_on_hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_act_break_on_hit->setIconVisibleInMenu(bp_break_count != 0);
|
||||||
|
m_act_break_on_hit->setIcon(bp_break_count == m_index_list_temp.size() ? m_icn_full :
|
||||||
|
m_icn_partial);
|
||||||
|
m_act_log_on_hit->setIconVisibleInMenu(bp_log_count != 0);
|
||||||
|
m_act_log_on_hit->setIcon(bp_log_count == m_index_list_temp.size() ? m_icn_full :
|
||||||
|
m_icn_partial);
|
||||||
|
m_act_both_on_hit->setIconVisibleInMenu(bp_both_count != 0);
|
||||||
|
m_act_both_on_hit->setIcon(bp_both_count == m_index_list_temp.size() ? m_icn_full :
|
||||||
|
m_icn_partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_mnu_table_context;
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,11 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
|
#include <QIcon>
|
||||||
#include <QModelIndexList>
|
#include <QModelIndexList>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
namespace Core
|
namespace Core
|
||||||
{
|
{
|
||||||
class BranchWatch;
|
class BranchWatch;
|
||||||
|
@ -77,6 +80,7 @@ private:
|
||||||
void OnWipeInspection();
|
void OnWipeInspection();
|
||||||
void OnTimeout();
|
void OnTimeout();
|
||||||
void OnEmulationStateChanged(Core::State new_state);
|
void OnEmulationStateChanged(Core::State new_state);
|
||||||
|
void OnThemeChanged();
|
||||||
void OnHelp();
|
void OnHelp();
|
||||||
void OnToggleAutoSave(bool checked);
|
void OnToggleAutoSave(bool checked);
|
||||||
void OnHideShowControls(bool checked);
|
void OnHideShowControls(bool checked);
|
||||||
|
@ -85,11 +89,14 @@ private:
|
||||||
void OnTableClicked(const QModelIndex& index);
|
void OnTableClicked(const QModelIndex& index);
|
||||||
void OnTableContextMenu(const QPoint& pos);
|
void OnTableContextMenu(const QPoint& pos);
|
||||||
void OnTableHeaderContextMenu(const QPoint& pos);
|
void OnTableHeaderContextMenu(const QPoint& pos);
|
||||||
void OnTableDelete(QModelIndexList index_list);
|
void OnTableDelete();
|
||||||
void OnTableDeleteKeypress();
|
void OnTableDeleteKeypress();
|
||||||
void OnTableSetBLR(QModelIndexList index_list);
|
void OnTableSetBLR();
|
||||||
void OnTableSetNOP(QModelIndexList index_list);
|
void OnTableSetNOP();
|
||||||
void OnTableCopyAddress(QModelIndexList index_list);
|
void OnTableCopyAddress();
|
||||||
|
void OnTableSetBreakpointBreak();
|
||||||
|
void OnTableSetBreakpointLog();
|
||||||
|
void OnTableSetBreakpointBoth();
|
||||||
|
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
|
||||||
|
@ -99,9 +106,14 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateStatus();
|
void UpdateStatus();
|
||||||
|
void UpdateIcons();
|
||||||
void Save(const Core::CPUThreadGuard& guard, const std::string& filepath);
|
void Save(const Core::CPUThreadGuard& guard, const std::string& filepath);
|
||||||
void Load(const Core::CPUThreadGuard& guard, const std::string& filepath);
|
void Load(const Core::CPUThreadGuard& guard, const std::string& filepath);
|
||||||
void AutoSave(const Core::CPUThreadGuard& guard);
|
void AutoSave(const Core::CPUThreadGuard& guard);
|
||||||
|
void SetStubPatches(u32 value) const;
|
||||||
|
void SetBreakpoints(bool break_on_hit, bool log_on_hit) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QMenu* GetTableContextMenu(const QModelIndex& index);
|
||||||
|
|
||||||
Core::System& m_system;
|
Core::System& m_system;
|
||||||
Core::BranchWatch& m_branch_watch;
|
Core::BranchWatch& m_branch_watch;
|
||||||
|
@ -110,6 +122,14 @@ private:
|
||||||
QPushButton *m_btn_start_pause, *m_btn_clear_watch, *m_btn_path_was_taken, *m_btn_path_not_taken,
|
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;
|
*m_btn_was_overwritten, *m_btn_not_overwritten, *m_btn_wipe_recent_hits;
|
||||||
QAction* m_act_autosave;
|
QAction* m_act_autosave;
|
||||||
|
QAction* m_act_insert_nop;
|
||||||
|
QAction* m_act_insert_blr;
|
||||||
|
QAction* m_act_copy_address;
|
||||||
|
QMenu* m_mnu_set_breakpoint;
|
||||||
|
QAction* m_act_break_on_hit;
|
||||||
|
QAction* m_act_log_on_hit;
|
||||||
|
QAction* m_act_both_on_hit;
|
||||||
|
QMenu* m_mnu_table_context = nullptr;
|
||||||
QMenu* m_mnu_column_visibility;
|
QMenu* m_mnu_column_visibility;
|
||||||
|
|
||||||
QToolBar* m_control_toolbar;
|
QToolBar* m_control_toolbar;
|
||||||
|
@ -119,5 +139,8 @@ private:
|
||||||
QStatusBar* m_status_bar;
|
QStatusBar* m_status_bar;
|
||||||
QTimer* m_timer;
|
QTimer* m_timer;
|
||||||
|
|
||||||
|
QIcon m_icn_full, m_icn_partial;
|
||||||
|
|
||||||
|
QModelIndexList m_index_list_temp;
|
||||||
std::optional<std::string> m_autosave_filepath;
|
std::optional<std::string> m_autosave_filepath;
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,18 +145,6 @@ void BranchWatchTableModel::OnWipeInspection()
|
||||||
roles);
|
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
|
void BranchWatchTableModel::Save(const Core::CPUThreadGuard& guard, std::FILE* file) const
|
||||||
{
|
{
|
||||||
m_branch_watch.Save(guard, file);
|
m_branch_watch.Save(guard, file);
|
||||||
|
|
|
@ -97,7 +97,6 @@ public:
|
||||||
void OnBranchNotOverwritten(const Core::CPUThreadGuard& guard);
|
void OnBranchNotOverwritten(const Core::CPUThreadGuard& guard);
|
||||||
void OnWipeRecentHits();
|
void OnWipeRecentHits();
|
||||||
void OnWipeInspection();
|
void OnWipeInspection();
|
||||||
void OnDelete(QModelIndexList index_list);
|
|
||||||
|
|
||||||
void Save(const Core::CPUThreadGuard& guard, std::FILE* file) const;
|
void Save(const Core::CPUThreadGuard& guard, std::FILE* file) const;
|
||||||
void Load(const Core::CPUThreadGuard& guard, std::FILE* file);
|
void Load(const Core::CPUThreadGuard& guard, std::FILE* file);
|
||||||
|
|
Loading…
Reference in New Issue