BranchWatchDialog: A Total Replacement for CodeDiffDialog
With a purpose-built Branch Watch feature built into the emulated system: BranchWatchDialog, replacing CodeDiffDialog, is now better than ever!
This commit is contained in:
parent
fd8f2c7822
commit
8134c8a572
|
@ -75,6 +75,8 @@
|
|||
#define DUMP_AUDIO_DIR "Audio"
|
||||
#define DUMP_DSP_DIR "DSP"
|
||||
#define DUMP_SSL_DIR "SSL"
|
||||
#define DUMP_DEBUG_DIR "Debug"
|
||||
#define DUMP_DEBUG_BRANCHWATCH_DIR "BranchWatch"
|
||||
#define LOGS_DIR "Logs"
|
||||
#define MAIL_LOGS_DIR "Mail"
|
||||
#define SHADERS_DIR "Shaders"
|
||||
|
|
|
@ -856,6 +856,9 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
|||
s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_BRANCHWATCH_IDX] =
|
||||
s_user_paths[D_DUMPDEBUG_IDX] + DUMP_DEBUG_BRANCHWATCH_DIR DIR_SEP;
|
||||
s_user_paths[D_LOGS_IDX] = s_user_paths[D_USER_IDX] + LOGS_DIR DIR_SEP;
|
||||
s_user_paths[D_MAILLOGS_IDX] = s_user_paths[D_LOGS_IDX] + MAIL_LOGS_DIR DIR_SEP;
|
||||
s_user_paths[D_THEMES_IDX] = s_user_paths[D_USER_IDX] + THEMES_DIR DIR_SEP;
|
||||
|
@ -932,6 +935,9 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
|||
s_user_paths[D_DUMPTEXTURES_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_TEXTURES_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDSP_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DSP_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPSSL_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_SSL_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_IDX] = s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_DIR DIR_SEP;
|
||||
s_user_paths[D_DUMPDEBUG_BRANCHWATCH_IDX] =
|
||||
s_user_paths[D_DUMP_IDX] + DUMP_DEBUG_BRANCHWATCH_DIR DIR_SEP;
|
||||
s_user_paths[F_MEM1DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM1_DUMP;
|
||||
s_user_paths[F_MEM2DUMP_IDX] = s_user_paths[D_DUMP_IDX] + MEM2_DUMP;
|
||||
s_user_paths[F_ARAMDUMP_IDX] = s_user_paths[D_DUMP_IDX] + ARAM_DUMP;
|
||||
|
|
|
@ -52,6 +52,8 @@ enum
|
|||
D_DUMPTEXTURES_IDX,
|
||||
D_DUMPDSP_IDX,
|
||||
D_DUMPSSL_IDX,
|
||||
D_DUMPDEBUG_IDX,
|
||||
D_DUMPDEBUG_BRANCHWATCH_IDX,
|
||||
D_LOAD_IDX,
|
||||
D_LOGS_IDX,
|
||||
D_MAILLOGS_IDX,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Debugger/BranchWatch.h"
|
||||
#include "Core/HLE/HLE.h"
|
||||
#include "Core/HW/DVD/DVDInterface.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
|
@ -158,6 +159,11 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
|||
|
||||
auto& ppc_state = system.GetPPCState();
|
||||
auto& mmu = system.GetMMU();
|
||||
auto& branch_watch = system.GetPowerPC().GetBranchWatch();
|
||||
|
||||
const bool resume_branch_watch = branch_watch.GetRecordingActive();
|
||||
if (system.IsBranchWatchIgnoreApploader())
|
||||
branch_watch.Pause();
|
||||
|
||||
// Call iAppLoaderEntry.
|
||||
DEBUG_LOG_FMT(BOOT, "Call iAppLoaderEntry");
|
||||
|
@ -220,6 +226,8 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
|
|||
// return
|
||||
ppc_state.pc = ppc_state.gpr[3];
|
||||
|
||||
branch_watch.SetRecordingActive(resume_branch_watch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -141,9 +141,11 @@ public:
|
|||
bool IsPauseOnPanicMode() const { return m_pause_on_panic_enabled; }
|
||||
bool IsMIOS() const { return m_is_mios; }
|
||||
bool IsWii() const { return m_is_wii; }
|
||||
bool IsBranchWatchIgnoreApploader() { return m_branch_watch_ignore_apploader; }
|
||||
|
||||
void SetIsMIOS(bool is_mios) { m_is_mios = is_mios; }
|
||||
void SetIsWii(bool is_wii) { m_is_wii = is_wii; }
|
||||
void SetIsBranchWatchIgnoreApploader(bool enable) { m_branch_watch_ignore_apploader = enable; }
|
||||
|
||||
SoundStream* GetSoundStream() const;
|
||||
void SetSoundStream(std::unique_ptr<SoundStream> sound_stream);
|
||||
|
@ -202,5 +204,6 @@ private:
|
|||
bool m_pause_on_panic_enabled = false;
|
||||
bool m_is_mios = false;
|
||||
bool m_is_wii = false;
|
||||
bool m_branch_watch_ignore_apploader = false;
|
||||
};
|
||||
} // namespace Core
|
||||
|
|
|
@ -206,12 +206,14 @@ add_executable(dolphin-emu
|
|||
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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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()
|
||||
|
@ -394,6 +394,10 @@ void CodeWidget::UpdateSymbols()
|
|||
}
|
||||
|
||||
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)
|
||||
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -137,9 +137,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" />
|
||||
|
@ -349,9 +350,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" />
|
||||
|
|
|
@ -74,6 +74,8 @@ static void CreateDumpPath(std::string path)
|
|||
File::CreateFullPath(File::GetUserPath(D_DUMPFRAMES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPOBJECTS_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX));
|
||||
}
|
||||
|
||||
static void CreateLoadPath(std::string path)
|
||||
|
@ -253,6 +255,8 @@ void CreateDirectories()
|
|||
File::CreateFullPath(File::GetUserPath(D_DUMPDSP_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPSSL_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPTEXTURES_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_GAMESETTINGS_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX));
|
||||
File::CreateFullPath(File::GetUserPath(D_GCUSER_IDX) + USA_DIR DIR_SEP);
|
||||
|
|
Loading…
Reference in New Issue