2023-12-07 09:36:02 -08:00
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Debugger/BranchWatchDialog.h"
#include <algorithm>
#include <optional>
2024-05-24 17:26:14 -07:00
#include <ranges>
2023-12-07 09:36:02 -08:00
#include <utility>
#include <QApplication>
#include <QCheckBox>
#include <QClipboard>
#include <QGridLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#include <QMenuBar>
#include <QPushButton>
#include <QShortcut>
#include <QSortFilterProxyModel>
#include <QStatusBar>
#include <QString>
#include <QTableView>
#include <QTimer>
#include <QToolBar>
#include <QVBoxLayout>
#include <QVariant>
#include <fmt/format.h>
#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
2024-08-05 21:34:10 -07:00
#include "Common/Unreachable.h"
2023-12-07 09:36:02 -08:00
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/Debugger/BranchWatch.h"
#include "Core/Debugger/PPCDebugInterface.h"
#include "Core/PowerPC/Gekko.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "DolphinQt/Debugger/BranchWatchTableModel.h"
#include "DolphinQt/Debugger/CodeWidget.h"
2024-03-16 23:05:56 -07:00
#include "DolphinQt/Host.h"
2023-12-07 09:36:02 -08:00
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
2024-05-24 17:31:54 -07:00
#include "DolphinQt/Resources.h"
2023-12-07 09:36:02 -08:00
#include "DolphinQt/Settings.h"
class BranchWatchProxyModel final : public QSortFilterProxyModel
{
friend BranchWatchDialog ;
public :
explicit BranchWatchProxyModel ( const Core :: BranchWatch & branch_watch , QObject * parent = nullptr )
: QSortFilterProxyModel ( parent ), m_branch_watch ( branch_watch )
{
}
2024-05-24 15:44:49 -07:00
~ BranchWatchProxyModel () override = default ;
BranchWatchProxyModel ( const BranchWatchProxyModel & ) = delete ;
BranchWatchProxyModel ( BranchWatchProxyModel && ) = delete ;
BranchWatchProxyModel & operator = ( const BranchWatchProxyModel & ) = delete ;
BranchWatchProxyModel & operator = ( BranchWatchProxyModel && ) = delete ;
2023-12-07 09:36:02 -08:00
BranchWatchTableModel * sourceModel () const
{
return static_cast < BranchWatchTableModel *> ( QSortFilterProxyModel :: sourceModel ());
}
void setSourceModel ( BranchWatchTableModel * source_model )
{
QSortFilterProxyModel :: setSourceModel ( source_model );
}
// Virtual setSourceModel is forbidden for type-safety reasons. See sourceModel().
[[noreturn]] void setSourceModel ( QAbstractItemModel * source_model ) override { Crash (); }
bool filterAcceptsRow ( int source_row , const QModelIndex & source_parent ) const override ;
template < bool BranchWatchProxyModel ::* member >
void OnToggled ( bool enabled )
{
this ->* member = enabled ;
invalidateRowsFilter ();
}
template < QString BranchWatchProxyModel ::* member >
void OnSymbolTextChanged ( const QString & text )
{
this ->* member = text ;
invalidateRowsFilter ();
}
template < std :: optional < u32 > BranchWatchProxyModel ::* member >
void OnAddressTextChanged ( const QString & text )
{
bool ok = false ;
if ( const u32 value = text . toUInt ( & ok , 16 ); ok )
this ->* member = value ;
else
this ->* member = std :: nullopt ;
invalidateRowsFilter ();
}
bool IsBranchTypeAllowed ( UGeckoInstruction inst ) const ;
2024-08-31 12:36:24 -07:00
void SetInspected ( const QModelIndex & index ) const ;
2024-08-06 05:04:50 -07:00
const Core :: BranchWatchSelectionValueType &
GetBranchWatchSelection ( const QModelIndex & index ) const ;
2023-12-07 09:36:02 -08:00
private :
const Core :: BranchWatch & m_branch_watch ;
QString m_origin_symbol_name = {}, m_destin_symbol_name = {};
std :: optional < u32 > m_origin_min , m_origin_max , m_destin_min , m_destin_max ;
bool m_b = {}, m_bl = {}, m_bc = {}, m_bcl = {}, m_blr = {}, m_blrl = {}, m_bclr = {},
m_bclrl = {}, m_bctr = {}, m_bctrl = {}, m_bcctr = {}, m_bcctrl = {};
bool m_cond_true = {}, m_cond_false = {};
};
bool BranchWatchProxyModel :: filterAcceptsRow ( int source_row , const QModelIndex & ) const
{
const Core :: BranchWatch :: Selection :: value_type & value = m_branch_watch . GetSelection ()[ source_row ];
if ( value . condition )
{
if ( ! m_cond_true )
return false ;
}
else if ( ! m_cond_false )
return false ;
const Core :: BranchWatchCollectionKey & k = value . collection_ptr -> first ;
if ( ! IsBranchTypeAllowed ( k . original_inst ))
return false ;
if ( m_origin_min . has_value () && k . origin_addr < m_origin_min . value ())
return false ;
if ( m_origin_max . has_value () && k . origin_addr > m_origin_max . value ())
return false ;
if ( m_destin_min . has_value () && k . destin_addr < m_destin_min . value ())
return false ;
if ( m_destin_max . has_value () && k . destin_addr > m_destin_max . value ())
return false ;
if ( ! m_origin_symbol_name . isEmpty ())
{
if ( const QVariant & symbol_name_v = sourceModel () -> GetSymbolList ()[ source_row ]. origin_name ;
2024-05-24 17:05:14 -07:00
! symbol_name_v . isValid () || ! static_cast < const QString *> ( symbol_name_v . data ())
-> contains ( m_origin_symbol_name , Qt :: CaseInsensitive ))
2023-12-07 09:36:02 -08:00
return false ;
}
if ( ! m_destin_symbol_name . isEmpty ())
{
if ( const QVariant & symbol_name_v = sourceModel () -> GetSymbolList ()[ source_row ]. destin_name ;
2024-05-24 17:05:14 -07:00
! symbol_name_v . isValid () || ! static_cast < const QString *> ( symbol_name_v . data ())
-> contains ( m_destin_symbol_name , Qt :: CaseInsensitive ))
2023-12-07 09:36:02 -08:00
return false ;
}
return true ;
}
bool BranchWatchProxyModel :: IsBranchTypeAllowed ( UGeckoInstruction inst ) const
{
switch ( inst . OPCD )
{
case 18 :
2024-08-06 05:04:50 -07:00
return inst . LK ? m_bl : m_b ;
2023-12-07 09:36:02 -08:00
case 16 :
2024-08-06 05:04:50 -07:00
return inst . LK ? m_bcl : m_bc ;
2023-12-07 09:36:02 -08:00
case 19 :
switch ( inst . SUBOP10 )
{
case 16 :
if (( inst . BO & 0b10100 ) == 0b10100 ) // 1z1zz - Branch always
2024-08-06 05:04:50 -07:00
return inst . LK ? m_blrl : m_blr ;
return inst . LK ? m_bclrl : m_bclr ;
2023-12-07 09:36:02 -08:00
case 528 :
if (( inst . BO & 0b10100 ) == 0b10100 ) // 1z1zz - Branch always
2024-08-06 05:04:50 -07:00
return inst . LK ? m_bctrl : m_bctr ;
return inst . LK ? m_bcctrl : m_bcctr ;
2023-12-07 09:36:02 -08:00
}
}
return false ;
}
2024-08-31 12:36:24 -07:00
void BranchWatchProxyModel :: SetInspected ( const QModelIndex & index ) const
2023-12-07 09:36:02 -08:00
{
sourceModel () -> SetInspected ( mapToSource ( index ));
}
2024-08-06 05:04:50 -07:00
const Core :: BranchWatchSelectionValueType &
BranchWatchProxyModel :: GetBranchWatchSelection ( const QModelIndex & index ) const
{
return sourceModel () -> GetBranchWatchSelection ( mapToSource ( index ));
}
2023-12-07 09:36:02 -08:00
BranchWatchDialog :: BranchWatchDialog ( Core :: System & system , Core :: BranchWatch & branch_watch ,
2024-03-10 11:43:12 -07:00
PPCSymbolDB & ppc_symbol_db , CodeWidget * code_widget ,
QWidget * parent )
2023-12-07 09:36:02 -08:00
: QDialog ( parent ), m_system ( system ), m_branch_watch ( branch_watch ), m_code_widget ( code_widget )
{
setWindowTitle ( tr ( "Branch Watch Tool" ));
setWindowFlags (( windowFlags () | Qt :: WindowMinMaxButtonsHint ) & ~ Qt :: WindowContextHelpButtonHint );
2024-07-29 00:00:17 -07:00
// Branch Watch Table
2024-08-04 07:44:39 -07:00
m_table_view = new QTableView ( nullptr );
m_table_proxy = new BranchWatchProxyModel ( m_branch_watch , m_table_view );
m_table_model = new BranchWatchTableModel ( m_system , m_branch_watch , ppc_symbol_db , m_table_proxy );
m_table_proxy -> setSourceModel ( m_table_model );
2024-07-29 00:00:17 -07:00
m_table_proxy -> setSortRole ( UserRole :: SortRole );
m_table_proxy -> setSortCaseSensitivity ( Qt :: CaseInsensitive );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
m_table_view -> setModel ( m_table_proxy );
m_table_view -> setSortingEnabled ( true );
m_table_view -> sortByColumn ( Column :: Origin , Qt :: AscendingOrder );
m_table_view -> setSelectionMode ( QAbstractItemView :: ExtendedSelection );
m_table_view -> setSelectionBehavior ( QAbstractItemView :: SelectRows );
m_table_view -> setSizePolicy ( QSizePolicy :: Expanding , QSizePolicy :: Expanding );
m_table_view -> setContextMenuPolicy ( Qt :: CustomContextMenu );
m_table_view -> setEditTriggers ( QAbstractItemView :: NoEditTriggers );
m_table_view -> setCornerButtonEnabled ( false );
m_table_view -> verticalHeader () -> hide ();
m_table_view -> setColumnWidth ( Column :: Instruction , 50 );
m_table_view -> setColumnWidth ( Column :: Condition , 50 );
m_table_view -> setColumnWidth ( Column :: OriginSymbol , 250 );
m_table_view -> setColumnWidth ( Column :: DestinSymbol , 250 );
// The default column width (100 units) is fine for the rest.
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const horizontal_header = m_table_view -> horizontalHeader ();
2024-07-29 00:00:17 -07:00
horizontal_header -> setContextMenuPolicy ( Qt :: CustomContextMenu );
horizontal_header -> setStretchLastSection ( true );
horizontal_header -> setSectionsMovable ( true );
horizontal_header -> setFirstSectionMovable ( true );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
connect ( m_table_view , & QTableView :: clicked , this , & BranchWatchDialog :: OnTableClicked );
connect ( m_table_view , & QTableView :: customContextMenuRequested , this ,
& BranchWatchDialog :: OnTableContextMenu );
connect ( horizontal_header , & QHeaderView :: customContextMenuRequested , this ,
& BranchWatchDialog :: OnTableHeaderContextMenu );
connect ( new QShortcut ( QKeySequence ( Qt :: Key_Delete ), this ), & QShortcut :: activated , this ,
& BranchWatchDialog :: OnTableDeleteKeypress );
2023-12-07 09:36:02 -08:00
2024-08-04 05:54:28 -07:00
// Status Bar
2024-08-04 07:44:39 -07:00
m_status_bar = new QStatusBar ( nullptr );
2024-08-04 05:54:28 -07:00
m_status_bar -> setSizeGripEnabled ( false );
2023-12-07 09:36:02 -08:00
2024-08-04 06:04:29 -07:00
// Controls Toolbar
2024-08-04 07:44:39 -07:00
m_control_toolbar = new QToolBar ( nullptr );
2024-07-29 00:00:17 -07:00
{
2023-12-07 09:36:02 -08:00
// Tool Controls
2024-08-04 07:44:39 -07:00
m_btn_start_pause = new QPushButton ( tr ( "Start Branch Watch" ), nullptr );
2024-07-29 00:00:17 -07:00
connect ( m_btn_start_pause , & QPushButton :: toggled , this , & BranchWatchDialog :: OnStartPause );
m_btn_start_pause -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
m_btn_start_pause -> setCheckable ( true );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
m_btn_clear_watch = new QPushButton ( tr ( "Clear Branch Watch" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_clear_watch , & QPushButton :: clicked , this , & BranchWatchDialog :: OnClearBranchWatch );
2024-07-29 00:00:17 -07:00
m_btn_clear_watch -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
m_btn_path_was_taken = new QPushButton ( tr ( "Code Path Was Taken" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_path_was_taken , & QPushButton :: clicked , this ,
2024-07-29 00:00:17 -07:00
& BranchWatchDialog :: OnCodePathWasTaken );
m_btn_path_was_taken -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
m_btn_path_not_taken = new QPushButton ( tr ( "Code Path Not Taken" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_path_not_taken , & QPushButton :: clicked , this ,
2024-07-29 00:00:17 -07:00
& BranchWatchDialog :: OnCodePathNotTaken );
m_btn_path_not_taken -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const layout = new QGridLayout ( nullptr );
2024-08-04 05:54:28 -07:00
layout -> addWidget ( m_btn_start_pause , 0 , 0 );
layout -> addWidget ( m_btn_clear_watch , 1 , 0 );
layout -> addWidget ( m_btn_path_was_taken , 0 , 1 );
layout -> addWidget ( m_btn_path_not_taken , 1 , 1 );
2024-08-04 07:44:39 -07:00
auto * const group_box = new QGroupBox ( tr ( "Tool Controls" ), nullptr );
2024-07-29 00:00:17 -07:00
group_box -> setLayout ( layout );
group_box -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
m_control_toolbar -> addWidget ( group_box );
}
{
2023-12-07 09:36:02 -08:00
// Spacer
2024-08-04 07:44:39 -07:00
auto * const widget = new QWidget ( nullptr );
2024-07-29 00:00:17 -07:00
widget -> setSizePolicy ( QSizePolicy :: MinimumExpanding , QSizePolicy :: Preferred );
m_control_toolbar -> addWidget ( widget );
}
{
2023-12-07 09:36:02 -08:00
// Branch Type Filter Options
2024-08-04 07:44:39 -07:00
auto * const layout = new QGridLayout ( nullptr );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
const auto routine = [ this , layout ]( const QString & text , const QString & tooltip , int row ,
int column , void ( BranchWatchProxyModel ::* slot )( bool )) {
2024-08-04 07:44:39 -07:00
auto * const check_box = new QCheckBox ( text , nullptr );
2024-07-29 00:00:17 -07:00
check_box -> setToolTip ( tooltip );
layout -> addWidget ( check_box , row , column );
connect ( check_box , & QCheckBox :: toggled , [ this , slot ]( bool checked ) {
( m_table_proxy ->* slot )( checked );
UpdateStatus ();
});
check_box -> setChecked ( true );
};
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
// clang-format off
routine ( QStringLiteral ( "b" ), tr ( "Branch" ), 0 , 0 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_b > );
routine ( QStringLiteral ( "bl" ), tr ( "Branch (LR saved)" ), 0 , 1 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bl > );
routine ( QStringLiteral ( "bc" ), tr ( "Branch Conditional" ), 0 , 2 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bc > );
routine ( QStringLiteral ( "bcl" ), tr ( "Branch Conditional (LR saved)" ), 0 , 3 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bcl > );
routine ( QStringLiteral ( "blr" ), tr ( "Branch to Link Register" ), 1 , 0 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_blr > );
routine ( QStringLiteral ( "blrl" ), tr ( "Branch to Link Register (LR saved)" ), 1 , 1 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_blrl > );
routine ( QStringLiteral ( "bclr" ), tr ( "Branch Conditional to Link Register" ), 1 , 2 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bclr > );
routine ( QStringLiteral ( "bclrl" ), tr ( "Branch Conditional to Link Register (LR saved)" ), 1 , 3 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bclrl > );
routine ( QStringLiteral ( "bctr" ), tr ( "Branch to Count Register" ), 2 , 0 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bctr > );
routine ( QStringLiteral ( "bctrl" ), tr ( "Branch to Count Register (LR saved)" ), 2 , 1 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bctrl > );
routine ( QStringLiteral ( "bcctr" ), tr ( "Branch Conditional to Count Register" ), 2 , 2 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bcctr > );
routine ( QStringLiteral ( "bcctrl" ), tr ( "Branch Conditional to Count Register (LR saved)" ), 2 , 3 , & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_bcctrl > );
// clang-format on
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const group_box = new QGroupBox ( tr ( "Branch Type" ), nullptr );
2024-07-29 00:00:17 -07:00
group_box -> setLayout ( layout );
group_box -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-08-04 05:44:44 -07:00
m_act_branch_type_filters = m_control_toolbar -> addWidget ( group_box );
2024-07-29 00:00:17 -07:00
}
{
2023-12-07 09:36:02 -08:00
// Origin and Destination Filter Options
2024-08-04 07:44:39 -07:00
auto * const layout = new QGridLayout ( nullptr );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
const auto routine = [ this , layout ]( const QString & placeholder_text , int row , int column ,
int width ,
void ( BranchWatchProxyModel ::* slot )( const QString & )) {
2024-08-04 07:44:39 -07:00
auto * const line_edit = new QLineEdit ( nullptr );
2024-07-29 00:00:17 -07:00
layout -> addWidget ( line_edit , row , column , 1 , width );
connect ( line_edit , & QLineEdit :: textChanged , [ this , slot ]( const QString & text ) {
( m_table_proxy ->* slot )( text );
UpdateStatus ();
});
line_edit -> setPlaceholderText ( placeholder_text );
return line_edit ;
};
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
// clang-format off
routine ( tr ( "Origin Symbol" ), 0 , 0 , 1 , & BranchWatchProxyModel :: OnSymbolTextChanged <& BranchWatchProxyModel :: m_origin_symbol_name > );
routine ( tr ( "Origin Min" ), 1 , 0 , 1 , & BranchWatchProxyModel :: OnAddressTextChanged <& BranchWatchProxyModel :: m_origin_min > ) -> setMaxLength ( 8 );
routine ( tr ( "Origin Max" ), 2 , 0 , 1 , & BranchWatchProxyModel :: OnAddressTextChanged <& BranchWatchProxyModel :: m_origin_max > ) -> setMaxLength ( 8 );
routine ( tr ( "Destination Symbol" ), 0 , 1 , 1 , & BranchWatchProxyModel :: OnSymbolTextChanged <& BranchWatchProxyModel :: m_destin_symbol_name > );
routine ( tr ( "Destination Min" ), 1 , 1 , 1 , & BranchWatchProxyModel :: OnAddressTextChanged <& BranchWatchProxyModel :: m_destin_min > ) -> setMaxLength ( 8 );
routine ( tr ( "Destination Max" ), 2 , 1 , 1 , & BranchWatchProxyModel :: OnAddressTextChanged <& BranchWatchProxyModel :: m_destin_max > ) -> setMaxLength ( 8 );
// clang-format on
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const group_box = new QGroupBox ( tr ( "Origin and Destination" ), nullptr );
2024-07-29 00:00:17 -07:00
group_box -> setLayout ( layout );
group_box -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-08-04 05:44:44 -07:00
m_act_origin_destin_filters = m_control_toolbar -> addWidget ( group_box );
2024-07-29 00:00:17 -07:00
}
{
2023-12-07 09:36:02 -08:00
// Condition Filter Options
2024-08-04 07:44:39 -07:00
auto * const layout = new QVBoxLayout ( nullptr );
2024-07-29 00:00:17 -07:00
layout -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-07-29 00:00:17 -07:00
const auto routine = [ this , layout ]( const QString & text ,
void ( BranchWatchProxyModel ::* slot )( bool )) {
2024-08-04 07:44:39 -07:00
auto * const check_box = new QCheckBox ( text , nullptr );
2024-07-29 00:00:17 -07:00
layout -> addWidget ( check_box );
connect ( check_box , & QCheckBox :: toggled , [ this , slot ]( bool checked ) {
( m_table_proxy ->* slot )( checked );
UpdateStatus ();
});
check_box -> setChecked ( true );
return check_box ;
};
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
routine ( tr ( "true" ), & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_cond_true > )
2024-07-29 00:00:17 -07:00
-> setToolTip ( tr ( "This will also filter unconditional branches. \n "
"To filter for or against unconditional branches, \n "
"use the Branch Type filter options." ));
2024-08-04 07:44:39 -07:00
routine ( tr ( "false" ), & BranchWatchProxyModel :: OnToggled <& BranchWatchProxyModel :: m_cond_false > );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const group_box = new QGroupBox ( tr ( "Condition" ), nullptr );
2024-07-29 00:00:17 -07:00
group_box -> setLayout ( layout );
group_box -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-08-04 05:44:44 -07:00
m_act_condition_filters = m_control_toolbar -> addWidget ( group_box );
2024-07-29 00:00:17 -07:00
}
{
2023-12-07 09:36:02 -08:00
// Misc. Controls
2024-08-04 07:44:39 -07:00
m_btn_was_overwritten = new QPushButton ( tr ( "Branch Was Overwritten" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_was_overwritten , & QPushButton :: clicked , this ,
2024-07-29 00:00:17 -07:00
& BranchWatchDialog :: OnBranchWasOverwritten );
m_btn_was_overwritten -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
m_btn_not_overwritten = new QPushButton ( tr ( "Branch Not Overwritten" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_not_overwritten , & QPushButton :: clicked , this ,
2024-07-29 00:00:17 -07:00
& BranchWatchDialog :: OnBranchNotOverwritten );
m_btn_not_overwritten -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
m_btn_wipe_recent_hits = new QPushButton ( tr ( "Wipe Recent Hits" ), nullptr );
2024-08-08 07:41:33 -07:00
connect ( m_btn_wipe_recent_hits , & QPushButton :: clicked , this ,
2024-07-29 00:00:17 -07:00
& BranchWatchDialog :: OnWipeRecentHits );
m_btn_wipe_recent_hits -> setSizePolicy ( QSizePolicy :: Preferred , QSizePolicy :: Expanding );
m_btn_wipe_recent_hits -> setEnabled ( false );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const layout = new QVBoxLayout ( nullptr );
2024-08-04 05:54:28 -07:00
layout -> addWidget ( m_btn_was_overwritten );
layout -> addWidget ( m_btn_not_overwritten );
layout -> addWidget ( m_btn_wipe_recent_hits );
2024-08-04 07:44:39 -07:00
auto * const group_box = new QGroupBox ( tr ( "Misc. Controls" ), nullptr );
2024-07-29 00:00:17 -07:00
group_box -> setLayout ( layout );
group_box -> setAlignment ( Qt :: AlignHCenter );
2023-12-07 09:36:02 -08:00
2024-08-04 05:44:44 -07:00
m_act_misc_controls = m_control_toolbar -> addWidget ( group_box );
2024-07-29 00:00:17 -07:00
}
2023-12-07 09:36:02 -08:00
2024-08-05 21:34:10 -07:00
// Table Context Menus
auto * const delete_action = new QAction ( tr ( "&Delete" ), this );
connect ( delete_action , & QAction :: triggered , this , & BranchWatchDialog :: OnTableDelete );
2024-08-06 05:04:50 -07:00
m_act_invert_condition = new QAction ( tr ( "Invert &Condition" ), this );
connect ( m_act_invert_condition , & QAction :: triggered , this ,
& BranchWatchDialog :: OnTableInvertCondition );
m_act_invert_decrement_check = new QAction ( tr ( "Invert &Decrement Check" ), this );
connect ( m_act_invert_decrement_check , & QAction :: triggered , this ,
& BranchWatchDialog :: OnTableInvertDecrementCheck );
m_act_make_unconditional = new QAction ( tr ( "Make &Unconditional" ), this );
connect ( m_act_make_unconditional , & QAction :: triggered , this ,
& BranchWatchDialog :: OnTableMakeUnconditional );
2024-08-05 21:34:10 -07:00
m_act_copy_address = new QAction ( tr ( "&Copy Address" ), this );
connect ( m_act_copy_address , & QAction :: triggered , this , & BranchWatchDialog :: OnTableCopyAddress );
m_act_insert_nop = new QAction ( tr ( "Insert &NOP" ), this );
connect ( m_act_insert_nop , & QAction :: triggered , this , & BranchWatchDialog :: OnTableSetNOP );
m_act_insert_blr = new QAction ( tr ( "Insert &BLR" ), this );
connect ( m_act_insert_blr , & QAction :: triggered , this , & BranchWatchDialog :: OnTableSetBLR );
m_mnu_set_breakpoint = new QMenu ( tr ( "Set Brea&kpoint" ), this );
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 );
2024-08-06 05:04:50 -07:00
m_mnu_table_context_instruction = new QMenu ( this );
m_mnu_table_context_instruction -> addActions (
{ delete_action , m_act_invert_condition , m_act_invert_decrement_check });
m_mnu_table_context_condition = new QMenu ( this );
m_mnu_table_context_condition -> addActions ({ delete_action , m_act_make_unconditional });
2024-08-05 21:34:10 -07:00
m_mnu_table_context_origin = new QMenu ( this );
m_mnu_table_context_origin -> addActions (
{ delete_action , m_act_insert_nop , m_act_copy_address , m_mnu_set_breakpoint -> menuAction ()});
m_mnu_table_context_destin_or_symbol = new QMenu ( this );
m_mnu_table_context_destin_or_symbol -> addActions (
{ delete_action , m_act_insert_blr , m_act_copy_address , m_mnu_set_breakpoint -> menuAction ()});
m_mnu_table_context_other = new QMenu ( this );
m_mnu_table_context_other -> addAction ( delete_action );
2024-08-04 06:04:29 -07:00
LoadQSettings ();
// Column Visibility Menu
2024-08-04 07:44:39 -07:00
m_mnu_column_visibility = new QMenu ( this );
2024-08-04 06:04:29 -07:00
{
static constexpr std :: array < const char * , Column :: NumberOfColumns > headers = {
QT_TR_NOOP ( "Instruction" ), QT_TR_NOOP ( "Condition" ), 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" )};
for ( int column = 0 ; column < Column :: NumberOfColumns ; ++ column )
{
2024-08-04 07:44:39 -07:00
auto * const action =
2024-08-04 06:04:29 -07:00
m_mnu_column_visibility -> addAction ( tr ( headers [ column ]), [ this , column ]( bool enabled ) {
m_table_view -> setColumnHidden ( column , ! enabled );
});
action -> setChecked ( ! m_table_view -> isColumnHidden ( column ));
action -> setCheckable ( true );
}
}
2024-08-04 05:44:44 -07:00
// Toolbar Visibility Menu
auto * const toolbar_visibility_menu = new QMenu ( this );
{
const auto routine = [ toolbar_visibility_menu ]( const QString & text , QAction * toolbar_action ) {
auto * const menu_action =
toolbar_visibility_menu -> addAction ( text , toolbar_action , & QAction :: setVisible );
menu_action -> setChecked ( toolbar_action -> isVisible ());
menu_action -> setCheckable ( true );
};
routine ( tr ( "&Branch Type" ), m_act_branch_type_filters );
routine ( tr ( "&Origin and Destination" ), m_act_origin_destin_filters );
routine ( tr ( "&Condition" ), m_act_condition_filters );
routine ( tr ( "&Misc. Controls" ), m_act_misc_controls );
}
2024-08-04 06:04:29 -07:00
// Menu Bar
2024-08-04 07:44:39 -07:00
auto * const menu_bar = new QMenuBar ( nullptr );
2024-08-04 06:04:29 -07:00
menu_bar -> setNativeMenuBar ( false );
{
2024-08-04 07:44:39 -07:00
auto * const menu = menu_bar -> addMenu ( tr ( "&File" ));
2024-08-04 06:04:29 -07:00
menu -> addAction ( tr ( "&Save Branch Watch" ), this , & BranchWatchDialog :: OnSave );
menu -> addAction ( tr ( "Save Branch Watch &As..." ), this , & BranchWatchDialog :: OnSaveAs );
menu -> addAction ( tr ( "&Load Branch Watch" ), this , & BranchWatchDialog :: OnLoad );
menu -> addAction ( tr ( "Load Branch Watch &From..." ), this , & BranchWatchDialog :: OnLoadFrom );
m_act_autosave = menu -> addAction ( tr ( "A&uto Save" ));
m_act_autosave -> setCheckable ( true );
connect ( m_act_autosave , & QAction :: toggled , this , & BranchWatchDialog :: OnToggleAutoSave );
}
{
2024-08-04 07:44:39 -07:00
auto * const menu = menu_bar -> addMenu ( tr ( "&Tool" ));
2024-08-04 06:04:29 -07:00
menu -> setToolTipsVisible ( true );
menu -> addAction ( tr ( "Hide &Controls" ), this , & BranchWatchDialog :: OnHideShowControls )
-> setCheckable ( true );
2024-08-04 07:44:39 -07:00
auto * const act_ignore_apploader = menu -> addAction ( tr ( "Ignore &Apploader Branch Hits" ));
2024-08-04 06:04:29 -07:00
act_ignore_apploader -> setToolTip (
tr ( "This only applies to the initial boot of the emulated software." ));
act_ignore_apploader -> setChecked ( m_system . IsBranchWatchIgnoreApploader ());
act_ignore_apploader -> setCheckable ( true );
connect ( act_ignore_apploader , & QAction :: toggled , this ,
& BranchWatchDialog :: OnToggleIgnoreApploader );
menu -> addMenu ( m_mnu_column_visibility ) -> setText ( tr ( "Column &Visibility" ));
2024-08-04 05:44:44 -07:00
menu -> addMenu ( toolbar_visibility_menu ) -> setText ( tr ( "&Toolbar Visibility" ));
2024-08-04 06:04:29 -07:00
menu -> addAction ( tr ( "Wipe &Inspection Data" ), this , & BranchWatchDialog :: OnWipeInspection );
menu -> addAction ( tr ( "&Help" ), this , & BranchWatchDialog :: OnHelp );
}
2024-08-04 07:44:39 -07:00
connect ( m_timer = new QTimer ( this ), & QTimer :: timeout , this , & BranchWatchDialog :: OnTimeout );
2024-07-29 00:00:17 -07:00
connect ( m_table_proxy , & BranchWatchProxyModel :: layoutChanged , this ,
& BranchWatchDialog :: UpdateStatus );
2023-12-07 09:36:02 -08:00
2024-08-04 07:44:39 -07:00
auto * const main_layout = new QVBoxLayout ( nullptr );
2024-08-04 05:54:28 -07:00
main_layout -> setMenuBar ( menu_bar );
main_layout -> addWidget ( m_control_toolbar );
main_layout -> addWidget ( m_table_view );
main_layout -> addWidget ( m_status_bar );
2024-07-29 00:00:17 -07:00
setLayout ( main_layout );
2023-12-07 09:36:02 -08:00
}
2024-03-05 14:02:45 -08:00
BranchWatchDialog ::~ BranchWatchDialog ()
2023-12-07 09:36:02 -08:00
{
2024-08-04 06:04:29 -07:00
SaveQSettings ();
2023-12-07 09:36:02 -08:00
}
static constexpr int BRANCH_WATCH_TOOL_TIMER_DELAY_MS = 100 ;
static bool TimerCondition ( const Core :: BranchWatch & branch_watch , Core :: State state )
{
return branch_watch . GetRecordingActive () && state > Core :: State :: Paused ;
}
2024-03-05 14:02:45 -08:00
void BranchWatchDialog :: hideEvent ( QHideEvent * event )
2023-12-07 09:36:02 -08:00
{
2024-08-04 06:48:37 -07:00
Hide ();
2024-03-05 14:02:45 -08:00
QDialog :: hideEvent ( event );
2023-12-07 09:36:02 -08:00
}
2024-03-05 14:02:45 -08:00
void BranchWatchDialog :: showEvent ( QShowEvent * event )
2023-12-07 09:36:02 -08:00
{
2024-08-04 06:48:37 -07:00
Show ();
2024-03-05 14:02:45 -08:00
QDialog :: showEvent ( event );
2023-12-07 09:36:02 -08:00
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnStartPause ( bool checked ) const
2023-12-07 09:36:02 -08:00
{
2024-08-07 02:39:17 -07:00
m_branch_watch . SetRecordingActive ( Core :: CPUThreadGuard { m_system }, checked );
2023-12-07 09:36:02 -08:00
if ( checked )
{
m_btn_start_pause -> setText ( tr ( "Pause Branch Watch" ));
2024-03-28 11:35:13 -07:00
if ( Core :: GetState ( m_system ) > Core :: State :: Paused )
2023-12-07 09:36:02 -08:00
m_timer -> start ( BRANCH_WATCH_TOOL_TIMER_DELAY_MS );
}
else
{
m_btn_start_pause -> setText ( tr ( "Start Branch Watch" ));
2024-08-07 02:39:17 -07:00
if ( m_timer -> isActive ())
m_timer -> stop ();
2023-12-07 09:36:02 -08:00
}
Update ();
}
void BranchWatchDialog :: OnClearBranchWatch ()
{
{
const Core :: CPUThreadGuard guard { m_system };
m_table_model -> OnClearBranchWatch ( guard );
AutoSave ( guard );
}
m_btn_wipe_recent_hits -> setEnabled ( false );
UpdateStatus ();
}
static std :: string GetSnapshotDefaultFilepath ()
{
return fmt :: format ( "{}{}.txt" , File :: GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX ),
SConfig :: GetInstance (). GetGameID ());
}
void BranchWatchDialog :: OnSave ()
{
if ( ! m_branch_watch . CanSave ())
{
ModalMessageBox :: warning ( this , tr ( "Error" ), tr ( "There is nothing to save!" ));
return ;
}
Save ( Core :: CPUThreadGuard { m_system }, GetSnapshotDefaultFilepath ());
}
void BranchWatchDialog :: OnSaveAs ()
{
if ( ! m_branch_watch . CanSave ())
{
ModalMessageBox :: warning ( this , tr ( "Error" ), tr ( "There is nothing to save!" ));
return ;
}
const QString filepath = DolphinFileDialog :: getSaveFileName (
2024-07-28 14:24:38 +02:00
this , tr ( "Save Branch Watch Snapshot" ),
2023-12-07 09:36:02 -08:00
QString :: fromStdString ( File :: GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX )),
tr ( "Text file (*.txt);;All Files (*)" ));
if ( filepath . isEmpty ())
return ;
Save ( Core :: CPUThreadGuard { m_system }, filepath . toStdString ());
}
void BranchWatchDialog :: OnLoad ()
{
Load ( Core :: CPUThreadGuard { m_system }, GetSnapshotDefaultFilepath ());
}
void BranchWatchDialog :: OnLoadFrom ()
{
const QString filepath = DolphinFileDialog :: getOpenFileName (
2024-07-28 14:24:38 +02:00
this , tr ( "Load Branch Watch Snapshot" ),
2023-12-07 09:36:02 -08:00
QString :: fromStdString ( File :: GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX )),
tr ( "Text file (*.txt);;All Files (*)" ), nullptr , QFileDialog :: Option :: ReadOnly );
if ( filepath . isEmpty ())
return ;
Load ( Core :: CPUThreadGuard { m_system }, filepath . toStdString ());
}
void BranchWatchDialog :: OnCodePathWasTaken ()
{
{
const Core :: CPUThreadGuard guard { m_system };
m_table_model -> OnCodePathWasTaken ( guard );
AutoSave ( guard );
}
m_btn_wipe_recent_hits -> setEnabled ( true );
UpdateStatus ();
}
void BranchWatchDialog :: OnCodePathNotTaken ()
{
{
const Core :: CPUThreadGuard guard { m_system };
m_table_model -> OnCodePathNotTaken ( guard );
AutoSave ( guard );
}
UpdateStatus ();
}
void BranchWatchDialog :: OnBranchWasOverwritten ()
{
{
const Core :: CPUThreadGuard guard { m_system };
m_table_model -> OnBranchWasOverwritten ( guard );
AutoSave ( guard );
}
UpdateStatus ();
}
void BranchWatchDialog :: OnBranchNotOverwritten ()
{
{
const Core :: CPUThreadGuard guard { m_system };
m_table_model -> OnBranchNotOverwritten ( guard );
AutoSave ( guard );
}
UpdateStatus ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnWipeRecentHits () const
2023-12-07 09:36:02 -08:00
{
m_table_model -> OnWipeRecentHits ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnWipeInspection () const
2023-12-07 09:36:02 -08:00
{
m_table_model -> OnWipeInspection ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTimeout () const
2023-12-07 09:36:02 -08:00
{
Update ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnEmulationStateChanged ( Core :: State new_state ) const
2023-12-07 09:36:02 -08:00
{
2024-08-31 15:10:04 -07:00
m_btn_was_overwritten -> setEnabled ( new_state != Core :: State :: Uninitialized );
m_btn_not_overwritten -> setEnabled ( new_state != Core :: State :: Uninitialized );
2023-12-07 09:36:02 -08:00
if ( TimerCondition ( m_branch_watch , new_state ))
m_timer -> start ( BRANCH_WATCH_TOOL_TIMER_DELAY_MS );
else if ( m_timer -> isActive ())
m_timer -> stop ();
Update ();
}
2024-05-24 17:31:54 -07:00
void BranchWatchDialog :: OnThemeChanged ()
{
UpdateIcons ();
}
2023-12-07 09:36:02 -08:00
void BranchWatchDialog :: OnHelp ()
{
ModalMessageBox :: information (
this , tr ( "Branch Watch Tool Help (1/4)" ),
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 "
2024-05-24 17:31:54 -07:00
"Engine's Ultimap, Branch Watch is similar to that."
" \n\n "
2023-12-07 09:36:02 -08:00
"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 "
"from the User Directory to persist after Dolphin Emulator is closed. \" Save As... \" and "
" \" Load From... \" actions are also available, and auto-saving can be enabled to save a "
"snapshot at every step of a search. The \" Pause Branch Watch \" button will halt Branch "
"Watch from tracking further branch hits until it is told to resume. Press the \" Clear "
"Branch Watch \" button to clear all candidates and return to the blacklist phase." ));
ModalMessageBox :: information (
this , tr ( "Branch Watch Tool Help (2/4)" ),
tr ( "Branch Watch starts in the blacklist phase, meaning no candidates have been chosen yet, "
"but candidates found so far can be excluded from the candidacy by pressing the \" Code "
"Path Not Taken \" , \" Branch Was Overwritten \" , and \" Branch Not Overwritten \" buttons. "
"Once the \" Code Path Was Taken \" button is pressed for the first time, Branch Watch will "
"switch to the reduction phase, and the table will populate with all eligible "
"candidates." ));
ModalMessageBox :: information (
this , tr ( "Branch Watch Tool Help (3/4)" ),
tr ( "Once in the reduction phase, it is time to start narrowing down the candidates shown in "
"the table. Further reduce the candidates by checking whether a code path was or was not "
"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 "
"first hit. Filter the candidates by branch kind, branch condition, origin or destination "
2024-05-24 17:31:54 -07:00
"address, and origin or destination symbol name."
" \n\n "
2023-12-07 09:36:02 -08:00
"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 "
"software." ));
ModalMessageBox :: information (
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 "
"view the associated address in Code View. Right-clicking the selected row(s) will bring "
2024-05-24 17:31:54 -07:00
"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 "
2024-08-06 05:04:50 -07:00
"If the instruction column of a row selection is right-clicked, an action to invert the "
"branch instruction's condition and an action to invert the branch instruction's "
"decrement check will be available, but only if the branch instruction is a conditional "
"one."
" \n\n "
"If the condition column of a row selection is right-clicked, an action to make the "
"branch instruction unconditional will be available, but only if the branch instruction "
"is a conditional one."
" \n\n "
2023-12-07 09:36:02 -08:00
"If the origin column of a row selection is right-clicked, an action to replace the "
2024-05-24 17:31:54 -07:00
"branch instruction at the origin(s) with a NOP instruction (No Operation) will be "
"available."
" \n\n "
2023-12-07 09:36:02 -08:00
"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 "
2024-05-24 17:31:54 -07:00
"be available, but will only be enabled if the branch instruction at every origin updates "
"the link register."
" \n\n "
2023-12-07 09:36:02 -08:00
"If the origin / destination symbol column of a row selection is right-clicked, an action "
2024-05-24 17:31:54 -07:00
"to replace the instruction at the start of the symbol(s) with a BLR instruction will be "
"available, but will only be enabled if every row in the selection has a symbol."
" \n\n "
2023-12-07 09:36:02 -08:00
"All context menus have the action to delete the selected row(s) from the candidates." ));
}
void BranchWatchDialog :: OnToggleAutoSave ( bool checked )
{
if ( ! checked )
return ;
const QString filepath = DolphinFileDialog :: getSaveFileName (
2024-04-20 16:26:53 +02:00
// i18n: If the user selects a file, Branch Watch will save to that file.
// If the user presses Cancel, Branch Watch will save to a file in the user folder.
2024-07-28 14:24:38 +02:00
this , tr ( "Select Branch Watch Snapshot Auto-Save File (for user folder location, cancel)" ),
2023-12-07 09:36:02 -08:00
QString :: fromStdString ( File :: GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX )),
tr ( "Text file (*.txt);;All Files (*)" ));
if ( filepath . isEmpty ())
m_autosave_filepath = std :: nullopt ;
else
m_autosave_filepath = filepath . toStdString ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnHideShowControls ( bool checked ) const
2023-12-07 09:36:02 -08:00
{
if ( checked )
m_control_toolbar -> hide ();
else
m_control_toolbar -> show ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnToggleIgnoreApploader ( bool checked ) const
2023-12-07 09:36:02 -08:00
{
m_system . SetIsBranchWatchIgnoreApploader ( checked );
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableClicked ( const QModelIndex & index ) const
2023-12-07 09:36:02 -08:00
{
const QVariant v = m_table_proxy -> data ( index , UserRole :: ClickRole );
switch ( index . column ())
{
case Column :: OriginSymbol :
case Column :: DestinSymbol :
if ( ! v . isValid ())
return ;
[[fallthrough]] ;
case Column :: Origin :
case Column :: Destination :
m_code_widget -> SetAddress ( v . value < u32 > (), CodeViewWidget :: SetAddressUpdate :: WithDetailedUpdate );
return ;
}
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableContextMenu ( const QPoint & pos ) const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
if ( m_table_view -> horizontalHeader () -> hiddenSectionCount () == Column :: NumberOfColumns )
{
m_mnu_column_visibility -> exec ( m_table_view -> viewport () -> mapToGlobal ( pos ));
return ;
}
2023-12-07 09:36:02 -08:00
const QModelIndex index = m_table_view -> indexAt ( pos );
if ( ! index . isValid ())
return ;
2024-05-24 17:26:14 -07:00
m_index_list_temp = m_table_view -> selectionModel () -> selectedRows ( index . column ());
GetTableContextMenu ( index ) -> exec ( m_table_view -> viewport () -> mapToGlobal ( pos ));
m_index_list_temp . clear ();
m_index_list_temp . shrink_to_fit ();
2023-12-07 09:36:02 -08:00
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableHeaderContextMenu ( const QPoint & pos ) const
2023-12-07 09:36:02 -08:00
{
m_mnu_column_visibility -> exec ( m_table_view -> horizontalHeader () -> mapToGlobal ( pos ));
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableDelete () const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
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 ());
}
2023-12-07 09:36:02 -08:00
UpdateStatus ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableDeleteKeypress () const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
m_index_list_temp = m_table_view -> selectionModel () -> selectedRows ();
OnTableDelete ();
m_index_list_temp . clear ();
m_index_list_temp . shrink_to_fit ();
2023-12-07 09:36:02 -08:00
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableSetBLR () const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
SetStubPatches ( 0x4e800020 );
2023-12-07 09:36:02 -08:00
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableSetNOP () const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
SetStubPatches ( 0x60000000 );
2023-12-07 09:36:02 -08:00
}
2024-08-06 05:04:50 -07:00
void BranchWatchDialog :: OnTableInvertCondition () const
{
SetEditPatches ([]( u32 hex ) {
UGeckoInstruction inst = hex ;
inst . BO ^= 0b01000 ;
return inst . hex ;
});
}
void BranchWatchDialog :: OnTableInvertDecrementCheck () const
{
SetEditPatches ([]( u32 hex ) {
UGeckoInstruction inst = hex ;
inst . BO ^= 0b00010 ;
return inst . hex ;
});
}
void BranchWatchDialog :: OnTableMakeUnconditional () const
{
SetEditPatches ([]( u32 hex ) {
UGeckoInstruction inst = hex ;
inst . BO = 0b10100 ; // 1z1zz - Branch always
return inst . hex ;
});
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableCopyAddress () const
2023-12-07 09:36:02 -08:00
{
2024-05-24 17:26:14 -07:00
auto iter = m_index_list_temp . begin ();
if ( iter == m_index_list_temp . end ())
2023-12-07 09:36:02 -08:00
return ;
QString text ;
2024-05-24 17:26:14 -07:00
text . reserve ( m_index_list_temp . size () * 9 - 1 );
2023-12-07 09:36:02 -08:00
while ( true )
{
text . append ( QString :: number ( m_table_proxy -> data ( * iter , UserRole :: ClickRole ). value < u32 > (), 16 ));
2024-05-24 17:26:14 -07:00
if ( ++ iter == m_index_list_temp . end ())
2023-12-07 09:36:02 -08:00
break ;
text . append ( QChar :: fromLatin1 ( '\n' ));
}
QApplication :: clipboard () -> setText ( text );
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableSetBreakpointBreak () const
2024-05-24 17:31:54 -07:00
{
SetBreakpoints ( true , false );
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableSetBreakpointLog () const
2024-05-24 17:31:54 -07:00
{
SetBreakpoints ( false , true );
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: OnTableSetBreakpointBoth () const
2024-05-24 17:31:54 -07:00
{
SetBreakpoints ( true , true );
}
2024-08-04 06:48:37 -07:00
void BranchWatchDialog :: ConnectSlots ()
{
const auto * const settings = & Settings :: Instance ();
connect ( settings , & Settings :: EmulationStateChanged , this ,
& BranchWatchDialog :: OnEmulationStateChanged );
connect ( settings , & Settings :: ThemeChanged , this , & BranchWatchDialog :: OnThemeChanged );
connect ( settings , & Settings :: DebugFontChanged , m_table_model , & BranchWatchTableModel :: setFont );
const auto * const host = Host :: GetInstance ();
connect ( host , & Host :: PPCSymbolsChanged , m_table_model , & BranchWatchTableModel :: UpdateSymbols );
}
void BranchWatchDialog :: DisconnectSlots ()
{
const auto * const settings = & Settings :: Instance ();
disconnect ( settings , & Settings :: EmulationStateChanged , this ,
& BranchWatchDialog :: OnEmulationStateChanged );
disconnect ( settings , & Settings :: ThemeChanged , this , & BranchWatchDialog :: OnThemeChanged );
disconnect ( settings , & Settings :: DebugFontChanged , m_table_model ,
& BranchWatchTableModel :: OnDebugFontChanged );
const auto * const host = Host :: GetInstance ();
disconnect ( host , & Host :: PPCSymbolsChanged , m_table_model ,
& BranchWatchTableModel :: OnPPCSymbolsChanged );
}
void BranchWatchDialog :: Show ()
{
ConnectSlots ();
// Hit every slot that may have missed a signal while this widget was hidden.
OnEmulationStateChanged ( Core :: GetState ( m_system ));
OnThemeChanged ();
m_table_model -> OnDebugFontChanged ( Settings :: Instance (). GetDebugFont ());
m_table_model -> OnPPCSymbolsChanged ();
}
void BranchWatchDialog :: Hide ()
{
DisconnectSlots ();
if ( m_timer -> isActive ())
m_timer -> stop ();
}
2024-08-04 06:04:29 -07:00
void BranchWatchDialog :: LoadQSettings ()
{
const auto & settings = Settings :: GetQSettings ();
restoreGeometry ( settings . value ( QStringLiteral ( "branchwatchdialog/geometry" )). toByteArray ());
m_table_view -> horizontalHeader () -> restoreState ( // Restore column visibility state.
settings . value ( QStringLiteral ( "branchwatchdialog/tableheader/state" )). toByteArray ());
2024-08-04 05:44:44 -07:00
m_act_branch_type_filters -> setVisible (
! settings . value ( QStringLiteral ( "branchwatchdialog/toolbar/branch_type_hidden" )). toBool ());
m_act_origin_destin_filters -> setVisible (
! settings . value ( QStringLiteral ( "branchwatchdialog/toolbar/origin_destin_hidden" )). toBool ());
m_act_condition_filters -> setVisible (
! settings . value ( QStringLiteral ( "branchwatchdialog/toolbar/condition_hidden" )). toBool ());
m_act_misc_controls -> setVisible (
! settings . value ( QStringLiteral ( "branchwatchdialog/toolbar/misc_controls_hidden" )). toBool ());
2024-08-04 06:04:29 -07:00
}
void BranchWatchDialog :: SaveQSettings () const
2024-03-05 14:02:45 -08:00
{
auto & settings = Settings :: GetQSettings ();
settings . setValue ( QStringLiteral ( "branchwatchdialog/geometry" ), saveGeometry ());
settings . setValue ( QStringLiteral ( "branchwatchdialog/tableheader/state" ),
m_table_view -> horizontalHeader () -> saveState ());
2024-08-04 05:44:44 -07:00
settings . setValue ( QStringLiteral ( "branchwatchdialog/toolbar/branch_type_hidden" ),
! m_act_branch_type_filters -> isVisible ());
settings . setValue ( QStringLiteral ( "branchwatchdialog/toolbar/origin_destin_hidden" ),
! m_act_origin_destin_filters -> isVisible ());
settings . setValue ( QStringLiteral ( "branchwatchdialog/toolbar/condition_hidden" ),
! m_act_condition_filters -> isVisible ());
settings . setValue ( QStringLiteral ( "branchwatchdialog/toolbar/misc_controls_hidden" ),
! m_act_misc_controls -> isVisible ());
2024-03-05 14:02:45 -08:00
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: Update () const
2023-12-07 09:36:02 -08:00
{
if ( m_branch_watch . GetRecordingPhase () == Core :: BranchWatch :: Phase :: Blacklist )
UpdateStatus ();
m_table_model -> UpdateHits ();
}
2024-08-31 12:36:24 -07:00
void BranchWatchDialog :: UpdateStatus () const
2023-12-07 09:36:02 -08:00
{
switch ( m_branch_watch . GetRecordingPhase ())
{
case Core :: BranchWatch :: Phase :: Blacklist :
{
const std :: size_t candidate_size = m_branch_watch . GetCollectionSize ();
const std :: size_t blacklist_size = m_branch_watch . GetBlacklistSize ();
if ( blacklist_size == 0 )
{
m_status_bar -> showMessage ( tr ( "Candidates: %1" ). arg ( candidate_size ));
return ;
}
m_status_bar -> showMessage ( tr ( "Candidates: %1 | Excluded: %2 | Remaining: %3" )
. arg ( candidate_size )
. arg ( blacklist_size )
. arg ( candidate_size - blacklist_size ));
return ;
}
case Core :: BranchWatch :: Phase :: Reduction :
{
const std :: size_t candidate_size = m_branch_watch . GetSelection (). size ();
if ( candidate_size == 0 )
{
m_status_bar -> showMessage ( tr ( "Zero candidates remaining." ));
return ;
}
const std :: size_t remaining_size = m_table_proxy -> rowCount ();
m_status_bar -> showMessage ( tr ( "Candidates: %1 | Filtered: %2 | Remaining: %3" )
. arg ( candidate_size )
. arg ( candidate_size - remaining_size )
. arg ( remaining_size ));
return ;
}
}
}
2024-05-24 17:31:54 -07:00
void BranchWatchDialog :: UpdateIcons ()
{
m_icn_full = Resources :: GetThemeIcon ( "debugger_breakpoint" );
m_icn_partial = Resources :: GetThemeIcon ( "stop" );
}
2023-12-07 09:36:02 -08:00
void BranchWatchDialog :: Save ( const Core :: CPUThreadGuard & guard , const std :: string & filepath )
{
File :: IOFile file ( filepath , "w" );
if ( ! file . IsOpen ())
{
ModalMessageBox :: warning (
this , tr ( "Error" ),
tr ( "Failed to save Branch Watch snapshot \" %1 \" " ). arg ( QString :: fromStdString ( filepath )));
return ;
}
m_table_model -> Save ( guard , file . GetHandle ());
}
void BranchWatchDialog :: Load ( const Core :: CPUThreadGuard & guard , const std :: string & filepath )
{
File :: IOFile file ( filepath , "r" );
if ( ! file . IsOpen ())
{
ModalMessageBox :: warning (
this , tr ( "Error" ),
tr ( "Failed to open Branch Watch snapshot \" %1 \" " ). arg ( QString :: fromStdString ( filepath )));
return ;
}
m_table_model -> Load ( guard , file . GetHandle ());
m_btn_wipe_recent_hits -> setEnabled ( m_branch_watch . GetRecordingPhase () ==
Core :: BranchWatch :: Phase :: Reduction );
}
void BranchWatchDialog :: AutoSave ( const Core :: CPUThreadGuard & guard )
{
if ( ! m_act_autosave -> isChecked () || ! m_branch_watch . CanSave ())
return ;
Save ( guard , m_autosave_filepath ? m_autosave_filepath . value () : GetSnapshotDefaultFilepath ());
}
2024-05-24 17:26:14 -07:00
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 ();
}
2024-08-06 05:04:50 -07:00
void BranchWatchDialog :: SetEditPatches ( u32 ( * transform )( u32 )) const
{
auto & debug_interface = m_system . GetPowerPC (). GetDebugInterface ();
for ( const Core :: CPUThreadGuard guard ( m_system ); const QModelIndex & index : m_index_list_temp )
{
const Core :: BranchWatchCollectionKey & k =
m_table_proxy -> GetBranchWatchSelection ( index ). collection_ptr -> first ;
// This function assumes patches apply to the origin address, unlike SetStubPatches.
debug_interface . SetPatch ( guard , k . origin_addr , transform ( k . original_inst . hex ));
m_table_proxy -> SetInspected ( index );
}
// TODO: Same issue as SetStubPatches.
m_code_widget -> Update ();
}
2024-05-24 17:31:54 -07:00
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 > ();
2024-06-15 11:36:38 +02:00
breakpoints . Add ( address , break_on_hit , log_on_hit , {});
2024-05-24 17:31:54 -07:00
}
2024-09-20 18:37:39 -07:00
emit Host :: GetInstance () -> PPCBreakpointsChanged ();
2024-05-24 17:31:54 -07:00
}
2024-08-05 21:34:10 -07:00
void BranchWatchDialog :: SetBreakpointMenuActionsIcons () const
2024-05-24 17:26:14 -07:00
{
2024-08-05 21:34:10 -07:00
qsizetype bp_break_count = 0 , bp_log_count = 0 , bp_both_count = 0 ;
for ( auto & breakpoints = m_system . GetPowerPC (). GetBreakPoints ();
const QModelIndex & index : m_index_list_temp )
2024-05-24 17:26:14 -07:00
{
2024-08-05 21:34:10 -07:00
if ( const TBreakPoint * bp = breakpoints . GetRegularBreakpoint (
m_table_proxy -> data ( index , UserRole :: ClickRole ). value < u32 > ()))
{
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 ;
}
2024-05-24 17:26:14 -07:00
}
2024-08-05 21:34:10 -07:00
const qsizetype selected_row_count = m_index_list_temp . size ();
m_act_break_on_hit -> setIconVisibleInMenu ( bp_break_count != 0 );
m_act_break_on_hit -> setIcon ( bp_break_count == selected_row_count ? 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 == selected_row_count ? 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 == selected_row_count ? m_icn_full : m_icn_partial );
}
2024-05-24 17:26:14 -07:00
2024-08-05 21:34:10 -07:00
QMenu * BranchWatchDialog :: GetTableContextMenu ( const QModelIndex & index ) const
{
2024-05-24 17:31:54 -07:00
const bool core_initialized = Core :: GetState ( m_system ) != Core :: State :: Uninitialized ;
2024-05-24 17:26:14 -07:00
switch ( index . column ())
{
2024-08-05 21:34:10 -07:00
case Column :: Instruction :
2024-08-06 05:04:50 -07:00
return GetTableContextMenu_Instruction ( core_initialized );
2024-08-05 21:34:10 -07:00
case Column :: Condition :
2024-08-06 05:04:50 -07:00
return GetTableContextMenu_Condition ( core_initialized );
2024-05-24 17:26:14 -07:00
case Column :: Origin :
2024-08-05 21:34:10 -07:00
return GetTableContextMenu_Origin ( core_initialized );
2024-05-24 17:26:14 -07:00
case Column :: Destination :
2024-08-05 21:34:10 -07:00
return GetTableContextMenu_Destin ( core_initialized );
case Column :: RecentHits :
case Column :: TotalHits :
return m_mnu_table_context_other ;
2024-05-24 17:26:14 -07:00
case Column :: OriginSymbol :
case Column :: DestinSymbol :
2024-08-05 21:34:10 -07:00
return GetTableContextMenu_Symbol ( core_initialized );
2024-05-24 17:26:14 -07:00
}
2024-08-05 21:34:10 -07:00
static_assert ( Column :: NumberOfColumns == 8 );
Common :: Unreachable ();
}
2024-05-24 17:31:54 -07:00
2024-08-06 05:04:50 -07:00
QMenu * BranchWatchDialog :: GetTableContextMenu_Instruction ( bool core_initialized ) const
{
const bool all_branches_conditional = // Taking advantage of short-circuit evaluation here.
core_initialized && std :: ranges :: all_of ( m_index_list_temp , [ this ]( const QModelIndex & index ) {
return BranchIsConditional (
m_table_proxy -> GetBranchWatchSelection ( index ). collection_ptr -> first . original_inst );
});
m_act_invert_condition -> setEnabled ( all_branches_conditional );
m_act_invert_decrement_check -> setEnabled ( all_branches_conditional );
return m_mnu_table_context_instruction ;
}
QMenu * BranchWatchDialog :: GetTableContextMenu_Condition ( bool core_initialized ) const
{
const bool all_branches_conditional = // Taking advantage of short-circuit evaluation here.
core_initialized && std :: ranges :: all_of ( m_index_list_temp , [ this ]( const QModelIndex & index ) {
return BranchIsConditional (
m_table_proxy -> GetBranchWatchSelection ( index ). collection_ptr -> first . original_inst );
});
m_act_make_unconditional -> setEnabled ( all_branches_conditional );
return m_mnu_table_context_condition ;
}
2024-08-05 21:34:10 -07:00
QMenu * BranchWatchDialog :: GetTableContextMenu_Origin ( bool core_initialized ) const
{
SetBreakpointMenuActionsIcons ();
m_act_insert_nop -> setEnabled ( core_initialized );
m_act_copy_address -> setEnabled ( true );
m_mnu_set_breakpoint -> setEnabled ( true );
return m_mnu_table_context_origin ;
}
2024-05-24 17:31:54 -07:00
2024-08-05 21:34:10 -07:00
QMenu * BranchWatchDialog :: GetTableContextMenu_Destin ( bool core_initialized ) const
{
SetBreakpointMenuActionsIcons ();
const bool all_branches_save_lr = // Taking advantage of short-circuit evaluation here.
core_initialized && std :: ranges :: all_of ( m_index_list_temp , [ this ]( const QModelIndex & index ) {
2024-08-06 05:04:50 -07:00
return m_table_proxy -> GetBranchWatchSelection ( index ). collection_ptr -> first . original_inst . LK ;
2024-08-05 21:34:10 -07:00
});
m_act_insert_blr -> setEnabled ( all_branches_save_lr );
m_act_copy_address -> setEnabled ( true );
m_mnu_set_breakpoint -> setEnabled ( true );
return m_mnu_table_context_destin_or_symbol ;
}
QMenu * BranchWatchDialog :: GetTableContextMenu_Symbol ( bool core_initialized ) const
{
SetBreakpointMenuActionsIcons ();
const bool all_symbols_valid =
std :: ranges :: all_of ( m_index_list_temp , [ this ]( const QModelIndex & index ) {
return m_table_proxy -> data ( index , UserRole :: ClickRole ). isValid ();
});
m_act_insert_blr -> setEnabled ( core_initialized && all_symbols_valid );
m_act_copy_address -> setEnabled ( all_symbols_valid );
m_mnu_set_breakpoint -> setEnabled ( all_symbols_valid );
return m_mnu_table_context_destin_or_symbol ;
2024-05-24 17:26:14 -07:00
}