pcsx2/pcsx2-qt/Settings/DebugAnalysisSettingsWidget...

505 lines
19 KiB
C++

// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "DebugAnalysisSettingsWidget.h"
#include "SettingsWindow.h"
#include "SettingWidgetBinder.h"
#include "DebugTools/SymbolImporter.h"
#include <QtWidgets/QFileDialog>
DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(QWidget* parent)
: QWidget(parent)
{
m_ui.setupUi(this);
m_ui.automaticallyClearSymbols->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "AutomaticallySelectSymbolsToClear", true));
setupSymbolSourceGrid();
m_ui.importFromElf->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymbolsFromELF", true));
m_ui.importSymFileFromDefaultLocation->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "ImportSymFileFromDefaultLocation", true));
m_ui.demangleSymbols->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleSymbols", true));
m_ui.demangleParameters->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "DemangleParameters", true));
setupSymbolFileList();
std::string function_scan_mode = Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanMode");
for (int i = 0;; i++)
{
if (Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames[i] == nullptr)
break;
if (function_scan_mode == Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames[i])
m_ui.functionScanMode->setCurrentIndex(i);
}
m_ui.customAddressRange->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "CustomFunctionScanRange", false));
m_ui.addressRangeStart->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanStartAddress", "")));
m_ui.addressRangeEnd->setText(QString::fromStdString(Host::GetStringSettingValue("Debugger/Analysis", "FunctionScanEndAddress", "")));
m_ui.grayOutOverwrittenFunctions->setChecked(Host::GetBoolSettingValue("Debugger/Analysis", "GenerateFunctionHashes", true));
connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
updateEnabledStates();
}
DebugAnalysisSettingsWidget::DebugAnalysisSettingsWidget(SettingsWindow* dialog, QWidget* parent)
: QWidget(parent)
, m_dialog(dialog)
{
SettingsInterface* sif = dialog->getSettingsInterface();
m_ui.setupUi(this);
// Make sure the user doesn't select symbol sources from both the global
// settings and the per-game settings, as these settings will conflict with
// each other. It only really makes sense to modify these settings on a
// per-game basis anyway.
if (dialog->isPerGameSettings())
{
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.automaticallyClearSymbols, "Debugger/Analysis", "AutomaticallySelectSymbolsToClear", true);
m_dialog->registerWidgetHelp(m_ui.automaticallyClearSymbols, tr("Automatically Select Symbols To Clear"), tr("Checked"),
tr("Automatically delete symbols that were generated by any previous analysis runs."));
setupSymbolSourceGrid();
}
else
{
m_ui.clearExistingSymbolsGroup->hide();
}
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.importFromElf, "Debugger/Analysis", "ImportSymbolsFromELF", true);
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.importSymFileFromDefaultLocation, "Debugger/Analysis", "ImportSymFileFromDefaultLocation", true);
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.demangleSymbols, "Debugger/Analysis", "DemangleSymbols", true);
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.demangleParameters, "Debugger/Analysis", "DemangleParameters", true);
m_dialog->registerWidgetHelp(m_ui.importFromElf, tr("Import From ELF"), tr("Checked"),
tr("Import symbol tables stored in the game's boot ELF."));
m_dialog->registerWidgetHelp(m_ui.importSymFileFromDefaultLocation, tr("Import Default .sym File"), tr("Checked"),
tr("Import symbols from a .sym file with the same name as the loaded ISO file on disk if such a file exists."));
m_dialog->registerWidgetHelp(m_ui.demangleSymbols, tr("Demangle Symbols"), tr("Checked"),
tr("Demangle C++ symbols during the import process so that the function and global variable names shown in the "
"debugger are more readable."));
m_dialog->registerWidgetHelp(m_ui.demangleParameters, tr("Demangle Parameters"), tr("Checked"),
tr("Include parameter lists in demangled function names."));
// Same as above. It only makes sense to load extra symbol files on a
// per-game basis.
if (dialog->isPerGameSettings())
{
setupSymbolFileList();
}
else
{
m_ui.symbolFileLabel->hide();
m_ui.symbolFileTable->hide();
m_ui.importSymbolFileButtons->hide();
}
SettingWidgetBinder::BindWidgetToEnumSetting(
sif, m_ui.functionScanMode, "Debugger/Analysis", "FunctionScanMode",
Pcsx2Config::DebugAnalysisOptions::FunctionScanModeNames, DebugFunctionScanMode::SCAN_ELF);
m_dialog->registerWidgetHelp(m_ui.functionScanMode, tr("Scan Mode"), tr("Scan ELF"),
tr("Choose where the function scanner looks to find functions. This option can be useful if the application "
"loads additional code at runtime."));
// Same as above. It only makes sense to set a custom memory range on a
// per-game basis.
if (dialog->isPerGameSettings())
{
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.customAddressRange, "Debugger/Analysis", "CustomFunctionScanRange", false);
connect(m_ui.addressRangeStart, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged);
connect(m_ui.addressRangeEnd, &QLineEdit::textChanged, this, &DebugAnalysisSettingsWidget::functionScanRangeChanged);
m_dialog->registerWidgetHelp(m_ui.customAddressRange, tr("Custom Address Range"), tr("Unchecked"),
tr("Whether to look for functions from the address range specified (Checked), or from the ELF segment "
"containing the entry point (Unchecked)."));
}
else
{
m_ui.customAddressRange->hide();
m_ui.customAddressRangeLineEdits->hide();
}
SettingWidgetBinder::BindWidgetToBoolSetting(
sif, m_ui.grayOutOverwrittenFunctions, "Debugger/Analysis", "GenerateFunctionHashes", true);
m_dialog->registerWidgetHelp(m_ui.grayOutOverwrittenFunctions, tr("Gray Out Symbols For Overwritten Functions"), tr("Checked"),
tr("Generate hashes for all the detected functions, and gray out the symbols displayed in the debugger for "
"functions that no longer match."));
connect(m_ui.automaticallyClearSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
connect(m_ui.demangleSymbols, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
connect(m_ui.customAddressRange, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::updateEnabledStates);
updateEnabledStates();
}
void DebugAnalysisSettingsWidget::parseSettingsFromWidgets(Pcsx2Config::DebugAnalysisOptions& output)
{
output.AutomaticallySelectSymbolsToClear = m_ui.automaticallyClearSymbols->isChecked();
for (const auto& [name, temp] : m_symbol_sources)
{
DebugSymbolSource& source = output.SymbolSources.emplace_back();
source.Name = name;
source.ClearDuringAnalysis = temp.check_box->isChecked();
}
output.ImportSymbolsFromELF = m_ui.importFromElf->isChecked();
output.ImportSymFileFromDefaultLocation = m_ui.importSymFileFromDefaultLocation->isChecked();
output.DemangleSymbols = m_ui.demangleSymbols->isChecked();
output.DemangleParameters = m_ui.demangleParameters->isChecked();
for (int i = 0; i < m_symbol_file_model->rowCount(); i++)
{
DebugExtraSymbolFile& file = output.ExtraSymbolFiles.emplace_back();
file.Path = m_symbol_file_model->item(i, PATH_COLUMN)->text().toStdString();
file.BaseAddress = m_symbol_file_model->item(i, BASE_ADDRESS_COLUMN)->text().toStdString();
file.Condition = m_symbol_file_model->item(i, CONDITION_COLUMN)->text().toStdString();
}
output.FunctionScanMode = static_cast<DebugFunctionScanMode>(m_ui.functionScanMode->currentIndex());
output.CustomFunctionScanRange = m_ui.customAddressRange->isChecked();
output.FunctionScanStartAddress = m_ui.addressRangeStart->text().toStdString();
output.FunctionScanEndAddress = m_ui.addressRangeEnd->text().toStdString();
}
void DebugAnalysisSettingsWidget::setupSymbolSourceGrid()
{
QGridLayout* layout = new QGridLayout(m_ui.symbolSourceGrid);
if (!m_dialog || m_dialog->getSerial() == QtHost::GetCurrentGameSerial().toStdString())
{
// Add symbol sources for which the user has already selected whether or
// not they should be cleared.
int existing_symbol_source_count = getIntSettingValue("Debugger/Analysis/SymbolSources", "Count", 0);
for (int i = 0; i < existing_symbol_source_count; i++)
{
std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i);
std::string name = getStringSettingValue(section.c_str(), "Name", "");
bool value = getBoolSettingValue(section.c_str(), "ClearDuringAnalysis", false);
SymbolSourceTemp& source = m_symbol_sources[name];
source.previous_value = value;
source.modified_by_user = true;
}
// Add any more symbol sources for which the user hasn't made a
// selection. These are separate since we don't want to have to store
// configuration data for them.
R5900SymbolGuardian.Read([&](const ccc::SymbolDatabase& database) {
for (const ccc::SymbolSource& symbol_source : database.symbol_sources)
{
if (m_symbol_sources.find(symbol_source.name()) == m_symbol_sources.end() && symbol_source.name() != "Built-In")
{
SymbolSourceTemp& source = m_symbol_sources[symbol_source.name()];
source.previous_value = SymbolImporter::ShouldClearSymbolsFromSourceByDefault(symbol_source.name());
source.modified_by_user = false;
}
}
});
if (m_symbol_sources.empty())
{
m_ui.symbolSourceErrorMessage->setText(tr("<i>No symbol sources in database.</i>"));
m_ui.symbolSourceScrollArea->hide();
return;
}
// Create the check boxes.
int i = 0;
for (auto& [name, temp] : m_symbol_sources)
{
temp.check_box = new QCheckBox(QString::fromStdString(name));
temp.check_box->setChecked(temp.previous_value);
layout->addWidget(temp.check_box, i / 2, i % 2);
connect(temp.check_box, &QCheckBox::checkStateChanged, this, &DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged);
i++;
}
}
else
{
m_ui.symbolSourceErrorMessage->setText(tr("<i>Start this game to modify the symbol sources list.</i>"));
m_ui.symbolSourceScrollArea->hide();
return;
}
m_ui.symbolSourceErrorMessage->hide();
}
void DebugAnalysisSettingsWidget::symbolSourceCheckStateChanged()
{
QCheckBox* check_box = qobject_cast<QCheckBox*>(sender());
if (!check_box)
return;
auto temp = m_symbol_sources.find(check_box->text().toStdString());
if (temp == m_symbol_sources.end())
return;
temp->second.modified_by_user = true;
saveSymbolSources();
}
void DebugAnalysisSettingsWidget::saveSymbolSources()
{
if (!m_dialog)
return;
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (!sif)
return;
// Clean up old configuration entries.
int old_count = sif->GetIntValue("Debugger/Analysis/SymbolSources", "Count");
for (int i = 0; i < old_count; i++)
{
std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i);
sif->RemoveSection(section.c_str());
}
sif->RemoveSection("Debugger/Analysis/SymbolSources");
int symbol_sources_to_save = 0;
for (auto& [name, temp] : m_symbol_sources)
if (temp.modified_by_user)
symbol_sources_to_save++;
if (symbol_sources_to_save == 0)
return;
// Make new configuration entries.
sif->SetIntValue("Debugger/Analysis/SymbolSources", "Count", symbol_sources_to_save);
int i = 0;
for (auto& [name, temp] : m_symbol_sources)
{
if (!temp.modified_by_user)
continue;
std::string section = "Debugger/Analysis/SymbolSources/" + std::to_string(i);
sif->SetStringValue(section.c_str(), "Name", name.c_str());
sif->SetBoolValue(section.c_str(), "ClearDuringAnalysis", temp.check_box->isChecked());
i++;
}
}
void DebugAnalysisSettingsWidget::setupSymbolFileList()
{
m_symbol_file_model = new QStandardItemModel(0, SYMBOL_FILE_COLUMN_COUNT, m_ui.symbolFileTable);
QStringList headers;
headers.emplace_back(tr("Path"));
headers.emplace_back(tr("Base Address"));
headers.emplace_back(tr("Condition"));
m_symbol_file_model->setHorizontalHeaderLabels(headers);
m_ui.symbolFileTable->setModel(m_symbol_file_model);
m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(PATH_COLUMN, QHeaderView::Stretch);
m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(BASE_ADDRESS_COLUMN, QHeaderView::Fixed);
m_ui.symbolFileTable->horizontalHeader()->setSectionResizeMode(CONDITION_COLUMN, QHeaderView::Fixed);
m_ui.symbolFileTable->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
int extra_symbol_file_count = getIntSettingValue("Debugger/Analysis/ExtraSymbolFiles", "Count", 0);
for (int i = 0; i < extra_symbol_file_count; i++)
{
std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i);
int row = m_symbol_file_model->rowCount();
if (!m_symbol_file_model->insertRow(row))
continue;
QStandardItem* path_item = new QStandardItem();
path_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "Path", "")));
m_symbol_file_model->setItem(row, PATH_COLUMN, path_item);
QStandardItem* base_address_item = new QStandardItem();
base_address_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "BaseAddress")));
m_symbol_file_model->setItem(row, BASE_ADDRESS_COLUMN, base_address_item);
QStandardItem* condition_item = new QStandardItem();
condition_item->setText(QString::fromStdString(getStringSettingValue(section.c_str(), "Condition")));
m_symbol_file_model->setItem(row, CONDITION_COLUMN, condition_item);
}
connect(m_ui.addSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::addSymbolFile);
connect(m_ui.removeSymbolFile, &QPushButton::clicked, this, &DebugAnalysisSettingsWidget::removeSymbolFile);
connect(m_ui.symbolFileTable->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &DebugAnalysisSettingsWidget::updateEnabledStates);
connect(m_symbol_file_model, &QStandardItemModel::dataChanged,
this, &DebugAnalysisSettingsWidget::saveSymbolFiles);
connect(m_symbol_file_model, &QStandardItemModel::dataChanged,
this, &DebugAnalysisSettingsWidget::updateEnabledStates);
}
void DebugAnalysisSettingsWidget::addSymbolFile()
{
std::string path = Path::ToNativePath(QFileDialog::getOpenFileName(this, tr("Add Symbol File")).toStdString());
if (path.empty())
return;
std::string relative_path = Path::MakeRelative(path, EmuFolders::GameSettings);
if (!relative_path.starts_with(".."))
path = std::move(relative_path);
int row = m_symbol_file_model->rowCount();
if (!m_symbol_file_model->insertRow(row))
return;
QStandardItem* path_item = new QStandardItem();
path_item->setText(QString::fromStdString(path));
m_symbol_file_model->setItem(row, PATH_COLUMN, path_item);
QStandardItem* base_address_item = new QStandardItem();
base_address_item->setText("");
m_symbol_file_model->setItem(row, BASE_ADDRESS_COLUMN, base_address_item);
QStandardItem* condition_item = new QStandardItem();
condition_item->setText("");
m_symbol_file_model->setItem(row, CONDITION_COLUMN, condition_item);
saveSymbolFiles();
updateEnabledStates();
}
void DebugAnalysisSettingsWidget::removeSymbolFile()
{
QItemSelectionModel* selection_model = m_ui.symbolFileTable->selectionModel();
if (!selection_model)
return;
while (!selection_model->selectedIndexes().isEmpty())
{
QModelIndex index = selection_model->selectedIndexes().first();
m_symbol_file_model->removeRow(index.row(), index.parent());
}
saveSymbolFiles();
updateEnabledStates();
}
void DebugAnalysisSettingsWidget::saveSymbolFiles()
{
if (!m_dialog)
return;
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (!sif)
return;
// Clean up old configuration entries.
int old_count = sif->GetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count");
for (int i = 0; i < old_count; i++)
{
std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i);
sif->RemoveSection(section.c_str());
}
sif->RemoveSection("Debugger/Analysis/ExtraSymbolFiles");
if (m_symbol_file_model->rowCount() == 0)
return;
// Make new configuration entries.
sif->SetIntValue("Debugger/Analysis/ExtraSymbolFiles", "Count", m_symbol_file_model->rowCount());
for (int i = 0; i < m_symbol_file_model->rowCount(); i++)
{
std::string section = "Debugger/Analysis/ExtraSymbolFiles/" + std::to_string(i);
if (QStandardItem* path_item = m_symbol_file_model->item(i, PATH_COLUMN))
sif->SetStringValue(section.c_str(), "Path", path_item->text().toStdString().c_str());
if (QStandardItem* base_address_item = m_symbol_file_model->item(i, BASE_ADDRESS_COLUMN))
sif->SetStringValue(section.c_str(), "BaseAddress", base_address_item->text().toStdString().c_str());
if (QStandardItem* condition_item = m_symbol_file_model->item(i, CONDITION_COLUMN))
sif->SetStringValue(section.c_str(), "Condition", condition_item->text().toStdString().c_str());
}
QtHost::SaveGameSettings(sif, true);
g_emu_thread->reloadGameSettings();
}
void DebugAnalysisSettingsWidget::functionScanRangeChanged()
{
if (!m_dialog)
return;
SettingsInterface* sif = m_dialog->getSettingsInterface();
if (!sif)
return;
QString start_address = m_ui.addressRangeStart->text();
QString end_address = m_ui.addressRangeEnd->text();
bool ok;
if (start_address.toUInt(&ok, 16), ok)
sif->SetStringValue("Debugger/Analysis", "FunctionScanStartAddress", start_address.toStdString().c_str());
if (end_address.toUInt(&ok, 16), ok)
sif->SetStringValue("Debugger/Analysis", "FunctionScanEndAddress", end_address.toStdString().c_str());
}
void DebugAnalysisSettingsWidget::updateEnabledStates()
{
m_ui.symbolSourceScrollArea->setEnabled(!m_ui.automaticallyClearSymbols->isChecked());
m_ui.symbolSourceErrorMessage->setEnabled(!m_ui.automaticallyClearSymbols->isChecked());
m_ui.demangleParameters->setEnabled(m_ui.demangleSymbols->isChecked());
m_ui.removeSymbolFile->setEnabled(
m_ui.symbolFileTable->selectionModel() && m_ui.symbolFileTable->selectionModel()->hasSelection());
m_ui.customAddressRangeLineEdits->setEnabled(m_ui.customAddressRange->isChecked());
}
std::string DebugAnalysisSettingsWidget::getStringSettingValue(
const char* section, const char* key, const char* default_value)
{
if (m_dialog)
return m_dialog->getEffectiveStringValue(section, key, default_value);
return Host::GetStringSettingValue(section, key, default_value);
}
bool DebugAnalysisSettingsWidget::getBoolSettingValue(
const char* section, const char* key, bool default_value)
{
if (m_dialog)
return m_dialog->getEffectiveBoolValue(section, key, default_value);
return Host::GetBoolSettingValue(section, key, default_value);
}
int DebugAnalysisSettingsWidget::getIntSettingValue(
const char* section, const char* key, int default_value)
{
if (m_dialog)
return m_dialog->getEffectiveIntValue(section, key, default_value);
return Host::GetIntSettingValue(section, key, default_value);
}