Debugger: Adds loading breakpoints/saved addresses from settings

Adds `/inis/debuggersettings/` settings folder to contain settings specifically for the debugger. Adds functionality to manually save (to settings) Breakpoints/Saved addresses and automatically load them upon launching the debugger.
This commit is contained in:
Dan McCarthy 2024-01-18 19:15:40 -06:00 committed by Connor McLaughlin
parent b402f6a404
commit 52ccc609cd
16 changed files with 513 additions and 155 deletions

1
.gitignore vendored
View File

@ -80,6 +80,7 @@ oprofile_data/
/bin/gamesettings
/bin/help
/bin/inis
/bin/inis/debuggersettings
/bin/logs
/bin/memcards
/bin/plugins

View File

@ -149,6 +149,8 @@ target_sources(pcsx2-qt PRIVATE
Debugger/CpuWidget.cpp
Debugger/CpuWidget.h
Debugger/CpuWidget.ui
Debugger/DebuggerSettingsManager.cpp
Debugger/DebuggerSettingsManager.h
Debugger/DebuggerWindow.cpp
Debugger/DebuggerWindow.h
Debugger/DebuggerWindow.ui

View File

@ -8,6 +8,7 @@
#include "Models/BreakpointModel.h"
#include "Models/ThreadModel.h"
#include "Models/SavedAddressesModel.h"
#include "Debugger/DebuggerSettingsManager.h"
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
@ -45,6 +46,15 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
m_ui.setupUi(this);
connect(g_emu_thread, &EmuThread::onVMPaused, this, &CpuWidget::onVMPaused);
connect(g_emu_thread, &EmuThread::onGameChanged, [this](const QString& title) {
if (title.isEmpty())
return;
// Don't overwrite users BPs/Saved Addresses unless they have a clean state.
if (m_bpModel.rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
if (m_savedAddressesModel.rowCount() == 0)
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
});
connect(m_ui.registerWidget, &RegisterWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
connect(m_ui.memoryviewWidget, &MemoryViewWidget::gotoInDisasm, m_ui.disassemblyWidget, &DisassemblyWidget::gotoAddress);
@ -143,9 +153,12 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu)
m_ui.savedAddressesList->horizontalHeader()->setSectionResizeMode(i++, mode);
}
QTableView* savedAddressesTableView = m_ui.savedAddressesList;
connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) {
connect(m_ui.savedAddressesList->model(), &QAbstractItemModel::dataChanged, [savedAddressesTableView](const QModelIndex& topLeft) {
savedAddressesTableView->resizeColumnToContents(topLeft.column());
});
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
}
CpuWidget::~CpuWidget() = default;
@ -301,46 +314,63 @@ void CpuWidget::onBPListDoubleClicked(const QModelIndex& index)
void CpuWidget::onBPListContextMenu(QPoint pos)
{
if (!m_cpu.isAlive())
return;
QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList);
QAction* newAction = new QAction(tr("New"), m_ui.breakpointList);
connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew);
contextMenu->addAction(newAction);
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (selModel->hasSelection())
if (m_cpu.isAlive())
{
QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList);
connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit);
contextMenu->addAction(editAction);
if (selModel->selectedIndexes().count() == 1)
QAction* newAction = new QAction(tr("New"), m_ui.breakpointList);
connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew);
contextMenu->addAction(newAction);
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
if (selModel->hasSelection())
{
QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList);
connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy);
contextMenu->addAction(copyAction);
}
QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList);
connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit);
contextMenu->addAction(editAction);
QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList);
connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete);
contextMenu->addAction(deleteAction);
if (selModel->selectedIndexes().count() == 1)
{
QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList);
connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy);
contextMenu->addAction(copyAction);
}
QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList);
connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete);
contextMenu->addAction(deleteAction);
}
}
contextMenu->addSeparator();
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList);
connect(actionExport, &QAction::triggered, [this]() {
// It's important to use the Export Role here to allow pasting to be translation agnostic
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), BreakpointModel::ExportRole, true));
});
contextMenu->addAction(actionExport);
if (m_bpModel.rowCount() > 0)
{
QAction* actionExport = new QAction(tr("Copy all as CSV"), m_ui.breakpointList);
connect(actionExport, &QAction::triggered, [this]() {
// It's important to use the Export Role here to allow pasting to be translation agnostic
QGuiApplication::clipboard()->setText(QtUtils::AbstractItemModelToCSV(m_ui.breakpointList->model(), BreakpointModel::ExportRole, true));
});
contextMenu->addAction(actionExport);
}
QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList);
connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV);
contextMenu->addAction(actionImport);
if (m_cpu.isAlive())
{
QAction* actionImport = new QAction(tr("Paste from CSV"), m_ui.breakpointList);
connect(actionImport, &QAction::triggered, this, &CpuWidget::contextBPListPasteCSV);
contextMenu->addAction(actionImport);
QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.breakpointList);
connect(actionLoad, &QAction::triggered, [this]() {
m_bpModel.clear();
DebuggerSettingsManager::loadGameSettings(&m_bpModel);
});
contextMenu->addAction(actionLoad);
QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.breakpointList);
connect(actionSave, &QAction::triggered, this, &CpuWidget::saveBreakpointsToDebuggerSettings);
contextMenu->addAction(actionSave);
}
contextMenu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos));
}
@ -407,7 +437,7 @@ void CpuWidget::contextBPListPasteCSV()
// In order to handle text with commas in them we must wrap values in quotes to mark
// where a value starts and end so that text commas aren't identified as delimiters.
// So matches each quote pair, parse it out, and removes the quotes to get the value.
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
while (it.hasNext())
{
@ -415,98 +445,7 @@ void CpuWidget::contextBPListPasteCSV()
QString matchedValue = match.captured(0);
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
if (fields.size() != BreakpointModel::BreakpointColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger CSV Import: Invalid number of columns, skipping");
continue;
}
bool ok;
const int type = fields[BreakpointModel::BreakpointColumns::TYPE].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
continue;
}
// This is how we differentiate between breakpoints and memchecks
if (type == MEMCHECK_INVALID)
{
BreakPoint bp;
// Address
bp.addr = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
continue;
}
// Condition
if (!fields[BreakpointModel::BreakpointColumns::CONDITION].isEmpty())
{
PostfixExpression expr;
bp.hasCond = true;
bp.cond.debug = &m_cpu;
if (!m_cpu.initExpression(fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), expr))
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond '%s', skipping", fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData());
continue;
}
bp.cond.expression = expr;
strncpy(&bp.cond.expressionString[0], fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), sizeof(bp.cond.expressionString));
}
// Enabled
bp.enabled = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse enable flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
continue;
}
m_bpModel.insertBreakpointRows(0, 1, {bp});
}
else
{
MemCheck mc;
// Mode
if (type >= MEMCHECK_INVALID)
{
Console.WriteLn("Debugger CSV Import: Failed to parse cond type '%s', skipping", fields [BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
continue;
}
mc.cond = static_cast<MemCheckCondition>(type);
// Address
mc.start = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
continue;
}
// Size
mc.end = fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUInt(&ok) + mc.start;
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse length '%s', skipping", fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUtf8().constData());
continue;
}
// Result
const int result = fields [BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse result flag '%s', skipping", fields [BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
continue;
}
mc.result = static_cast<MemCheckResult>(result);
m_bpModel.insertBreakpointRows(0, 1, {mc});
}
m_bpModel.loadBreakpointFromFieldList(fields);
}
}
@ -561,7 +500,19 @@ void CpuWidget::onSavedAddressesListContextMenu(QPoint pos)
connect(actionImportCSV, &QAction::triggered, this, &CpuWidget::contextSavedAddressesListPasteCSV);
contextMenu->addAction(actionImportCSV);
contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos));
if (m_cpu.isAlive())
{
QAction* actionLoad = new QAction(tr("Load from Settings"), m_ui.savedAddressesList);
connect(actionLoad, &QAction::triggered, [this]() {
m_savedAddressesModel.clear();
DebuggerSettingsManager::loadGameSettings(&m_savedAddressesModel);
});
contextMenu->addAction(actionLoad);
QAction* actionSave = new QAction(tr("Save to Settings"), m_ui.savedAddressesList);
connect(actionSave, &QAction::triggered, this, &CpuWidget::saveSavedAddressesToDebuggerSettings);
contextMenu->addAction(actionSave);
}
if (isIndexValid)
{
@ -571,6 +522,8 @@ void CpuWidget::onSavedAddressesListContextMenu(QPoint pos)
});
contextMenu->addAction(deleteAction);
}
contextMenu->popup(m_ui.savedAddressesList->viewport()->mapToGlobal(pos));
}
void CpuWidget::contextSavedAddressesListPasteCSV()
@ -594,24 +547,7 @@ void CpuWidget::contextSavedAddressesListPasteCSV()
fields << matchedValue.mid(1, matchedValue.length() - 2);
}
if (fields.size() != SavedAddressesModel::HeaderColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger CSV Import: Invalid number of columns, skipping");
continue;
}
bool ok;
const u32 address = fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger CSV Import: Failed to parse address '%s', skipping", fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUtf8().constData());
continue;
}
const QString label = fields[SavedAddressesModel::HeaderColumns::LABEL];
const QString description = fields[SavedAddressesModel::HeaderColumns::DESCRIPTION];
const SavedAddressesModel::SavedAddress importedAddress = {address, label, description};
m_savedAddressesModel.addRow(importedAddress);
m_savedAddressesModel.loadSavedAddressFromFieldList(fields);
}
}
@ -1392,3 +1328,12 @@ void CpuWidget::loadSearchResults()
}
}
void CpuWidget::saveBreakpointsToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_bpModel);
}
void CpuWidget::saveSavedAddressesToDebuggerSettings()
{
DebuggerSettingsManager::saveGameSettings(&m_savedAddressesModel);
}

View File

@ -118,6 +118,9 @@ public slots:
void contextRemoveSearchResult();
void onListSearchResultsContextMenu(QPoint pos);
void saveBreakpointsToDebuggerSettings();
void saveSavedAddressesToDebuggerSettings();
private:
std::vector<QTableWidget*> m_registerTableViews;
std::vector<u32> m_searchResults;

View File

@ -0,0 +1,164 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "DebuggerSettingsManager.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QFile>
#include "common/Console.h"
#include "fmt/core.h"
#include "VMManager.h"
#include "Models/BreakpointModel.h"
std::mutex DebuggerSettingsManager::writeLock;
const QString DebuggerSettingsManager::settingsFileVersion = "0.00";
QJsonObject DebuggerSettingsManager::loadGameSettingsJSON() {
std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::ReadOnly))
{
Console.WriteLnFmt("Debugger Settings Manager: No Debugger Settings file found for game at: '{}'", path);
return QJsonObject();
}
QByteArray fileContent = file.readAll();
file.close();
const QJsonDocument jsonDoc(QJsonDocument::fromJson(fileContent));
if (jsonDoc.isNull() || !jsonDoc.isObject())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to load contents of settings file for file at: '{}'", path);
return QJsonObject();
}
return jsonDoc.object();
}
void DebuggerSettingsManager::writeJSONToPath(std::string path, QJsonDocument jsonDocument)
{
QFile file(QString::fromStdString(path));
if (!file.open(QIODevice::WriteOnly))
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to write Debugger Settings file to path: '{}'", path);
return;
}
file.write(jsonDocument.toJson(QJsonDocument::Indented));
file.close();
}
void DebuggerSettingsManager::loadGameSettings(BreakpointModel* bpModel)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const QJsonValue breakpointsValue = loadGameSettingsJSON().value("Breakpoints");
const QString valueToLoad = breakpointsValue.toString();
if (breakpointsValue.isUndefined() || !breakpointsValue.isArray())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to read Breakpoints array from settings file: '{}'", path);
return;
}
const QJsonArray breakpointsArray = breakpointsValue.toArray();
for (u32 row = 0; row < breakpointsArray.size(); row++)
{
const QJsonValue rowValue = breakpointsArray.at(row);
if (rowValue.isUndefined() || !rowValue.isObject())
{
Console.WriteLn("Debugger Settings Manager: Failed to load invalid Breakpoint object.");
continue;
}
const QJsonObject rowObject = rowValue.toObject();
QStringList fields;
u32 col = 0;
for (auto iter = rowObject.begin(); iter != rowObject.end(); iter++, col++)
{
QString headerColKey = bpModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
fields << rowObject.value(headerColKey).toString();
}
bpModel->loadBreakpointFromFieldList(fields);
}
}
void DebuggerSettingsManager::loadGameSettings(SavedAddressesModel* savedAddressesModel)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const QJsonValue savedAddressesValue = loadGameSettingsJSON().value("SavedAddresses");
QString valueToLoad = savedAddressesValue.toString();
if (savedAddressesValue.isUndefined() || !savedAddressesValue.isArray())
{
Console.WriteLnFmt("Debugger Settings Manager: Failed to read Saved Addresses array from settings file: '{}'", path);
return;
}
const QJsonArray breakpointsArray = savedAddressesValue.toArray();
for (u32 row = 0; row < breakpointsArray.size(); row++)
{
const QJsonValue rowValue = breakpointsArray.at(row);
if (rowValue.isUndefined() || !rowValue.isObject())
{
Console.WriteLn("Debugger Settings Manager: Failed to load invalid Breakpoint object.");
continue;
}
const QJsonObject rowObject = rowValue.toObject();
QStringList fields;
u32 col = 0;
for (auto iter = rowObject.begin(); iter != rowObject.end(); iter++, col++)
{
QString headerColKey = savedAddressesModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
fields << rowObject.value(headerColKey).toString();
}
savedAddressesModel->loadSavedAddressFromFieldList(fields);
}
}
void DebuggerSettingsManager::saveGameSettings(BreakpointModel* bpModel)
{
saveGameSettings(bpModel, "Breakpoints", BreakpointModel::ExportRole);
}
void DebuggerSettingsManager::saveGameSettings(SavedAddressesModel* savedAddressesModel)
{
saveGameSettings(savedAddressesModel, "SavedAddresses", Qt::DisplayRole);
}
void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTableModel, QString settingsKey, u32 role)
{
const std::string path = VMManager::GetDebuggerSettingsFilePathForCurrentGame();
if (path.empty())
return;
const std::lock_guard<std::mutex> lock(writeLock);
QJsonObject loadedSettings = loadGameSettingsJSON();
QJsonArray rowsArray;
QStringList keys;
for (u32 col = 0; col < abstractTableModel->columnCount(); ++col)
{
keys << abstractTableModel->headerData(col, Qt::Horizontal, Qt::UserRole).toString();
}
for (u32 row = 0; row < abstractTableModel->rowCount(); row++)
{
QJsonObject rowObject;
for (u32 col = 0; col < abstractTableModel->columnCount(); col++)
{
const QModelIndex index = abstractTableModel->index(row, col);
const QString data = abstractTableModel->data(index, role).toString();
rowObject.insert(keys[col], QJsonValue::fromVariant(data));
}
rowsArray.append(rowObject);
}
loadedSettings.insert(settingsKey, rowsArray);
loadedSettings.insert("Version", settingsFileVersion);
QJsonDocument doc(loadedSettings);
writeJSONToPath(path, doc);
}

View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#pragma once
#include <QtWidgets/QDialog>
#include <mutex>
#include "Models/BreakpointModel.h"
#include "Models/SavedAddressesModel.h"
class DebuggerSettingsManager final
{
public:
DebuggerSettingsManager(QWidget* parent = nullptr);
~DebuggerSettingsManager();
static void loadGameSettings(BreakpointModel* bpModel);
static void loadGameSettings(SavedAddressesModel* savedAddressesModel);
static void saveGameSettings(BreakpointModel* bpModel);
static void saveGameSettings(SavedAddressesModel* savedAddressesModel);
static void saveGameSettings(QAbstractTableModel* abstractTableModel, QString settingsKey, u32 role);
private:
static std::mutex writeLock;
static void writeJSONToPath(std::string path, QJsonDocument jsonDocument);
static QJsonObject loadGameSettingsJSON();
const static QString settingsFileVersion;
};

View File

@ -6,6 +6,7 @@
#include "DebugTools/DebugInterface.h"
#include "DebugTools/Breakpoints.h"
#include "DebugTools/DisassemblyManager.h"
#include "common/Console.h"
#include "QtHost.h"
#include "QtUtils.h"
@ -226,6 +227,28 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i
return QVariant();
}
}
if (role == Qt::UserRole && orientation == Qt::Horizontal)
{
switch (section)
{
case BreakpointColumns::TYPE:
return "TYPE";
case BreakpointColumns::OFFSET:
return "OFFSET";
case BreakpointColumns::SIZE_LABEL:
return "SIZE / LABEL";
case BreakpointColumns::OPCODE:
return "INSTRUCTION";
case BreakpointColumns::CONDITION:
return "CONDITION";
case BreakpointColumns::HITS:
return "HITS";
case BreakpointColumns::ENABLED:
return "X";
default:
return QVariant();
}
}
return QVariant();
}
@ -402,3 +425,106 @@ void BreakpointModel::refreshData()
endResetModel();
}
void BreakpointModel::loadBreakpointFromFieldList(QStringList fields)
{
bool ok;
if (fields.size() != BreakpointModel::BreakpointColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger Breakpoint Model: Invalid number of columns, skipping");
return;
}
const int type = fields[BreakpointModel::BreakpointColumns::TYPE].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
return;
}
// This is how we differentiate between breakpoints and memchecks
if (type == MEMCHECK_INVALID)
{
BreakPoint bp;
// Address
bp.addr = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
return;
}
// Condition
if (!fields[BreakpointModel::BreakpointColumns::CONDITION].isEmpty())
{
PostfixExpression expr;
bp.hasCond = true;
bp.cond.debug = &m_cpu;
if (!m_cpu.initExpression(fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), expr))
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse cond '%s', skipping", fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData());
return;
}
bp.cond.expression = expr;
strncpy(&bp.cond.expressionString[0], fields[BreakpointModel::BreakpointColumns::CONDITION].toUtf8().constData(), sizeof(bp.cond.expressionString));
}
// Enabled
bp.enabled = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse enable flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
return;
}
insertBreakpointRows(0, 1, {bp});
}
else
{
MemCheck mc;
// Mode
if (type >= MEMCHECK_INVALID)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse cond type '%s', skipping", fields[BreakpointModel::BreakpointColumns::TYPE].toUtf8().constData());
return;
}
mc.cond = static_cast<MemCheckCondition>(type);
// Address
QString test = fields[BreakpointModel::BreakpointColumns::OFFSET];
mc.start = fields[BreakpointModel::BreakpointColumns::OFFSET].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse address '%s', skipping", fields[BreakpointModel::BreakpointColumns::OFFSET].toUtf8().constData());
return;
}
// Size
mc.end = fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUInt(&ok) + mc.start;
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse length '%s', skipping", fields[BreakpointModel::BreakpointColumns::SIZE_LABEL].toUtf8().constData());
return;
}
// Result
const int result = fields[BreakpointModel::BreakpointColumns::ENABLED].toUInt(&ok);
if (!ok)
{
Console.WriteLn("Debugger Breakpoint Model: Failed to parse result flag '%s', skipping", fields[BreakpointModel::BreakpointColumns::ENABLED].toUtf8().constData());
return;
}
mc.result = static_cast<MemCheckResult>(result);
insertBreakpointRows(0, 1, {mc});
}
}
void BreakpointModel::clear()
{
beginResetModel();
m_breakpoints.clear();
endResetModel();
}

View File

@ -55,10 +55,12 @@ public:
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
bool removeRows(int row, int count, const QModelIndex& index = QModelIndex()) override;
bool insertBreakpointRows(int row, int count, std::vector<BreakpointMemcheck> breakpoints, const QModelIndex& index = QModelIndex());
void loadBreakpointFromFieldList(QStringList breakpointFields);
BreakpointMemcheck at(int row) const { return m_breakpoints.at(row); };
void refreshData();
void clear();
private:
DebugInterface& m_cpu;

View File

@ -4,6 +4,8 @@
#include "PrecompiledHeader.h"
#include "SavedAddressesModel.h"
#include "common/Console.h"
SavedAddressesModel::SavedAddressesModel(DebugInterface& cpu, QObject* parent)
: QAbstractTableModel(parent)
, m_cpu(cpu)
@ -96,20 +98,38 @@ bool SavedAddressesModel::setData(const QModelIndex& index, const QVariant& valu
QVariant SavedAddressesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if ((role != Qt::DisplayRole && role != Qt::EditRole) || orientation != Qt::Horizontal)
if (orientation != Qt::Horizontal)
return QVariant();
switch (section)
if (role == Qt::DisplayRole)
{
case SavedAddressesModel::ADDRESS:
return tr("MEMORY ADDRESS");
case SavedAddressesModel::LABEL:
return tr("LABEL");
case SavedAddressesModel::DESCRIPTION:
return tr("DESCRIPTION");
default:
return QVariant();
switch (section)
{
case SavedAddressesModel::ADDRESS:
return tr("MEMORY ADDRESS");
case SavedAddressesModel::LABEL:
return tr("LABEL");
case SavedAddressesModel::DESCRIPTION:
return tr("DESCRIPTION");
default:
return QVariant();
}
}
if (role == Qt::UserRole)
{
switch (section)
{
case SavedAddressesModel::ADDRESS:
return "MEMORY ADDRESS";
case SavedAddressesModel::LABEL:
return "LABEL";
case SavedAddressesModel::DESCRIPTION:
return "DESCRIPTION";
default:
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags SavedAddressesModel::flags(const QModelIndex& index) const
@ -150,3 +170,32 @@ int SavedAddressesModel::columnCount(const QModelIndex&) const
{
return HeaderColumns::COLUMN_COUNT;
}
void SavedAddressesModel::loadSavedAddressFromFieldList(QStringList fields)
{
if (fields.size() != SavedAddressesModel::HeaderColumns::COLUMN_COUNT)
{
Console.WriteLn("Debugger Saved Addresses Model: Invalid number of columns, skipping");
return;
}
bool ok;
const u32 address = fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUInt(&ok, 16);
if (!ok)
{
Console.WriteLn("Debugger Saved Addresses Model: Failed to parse address '%s', skipping", fields[SavedAddressesModel::HeaderColumns::ADDRESS].toUtf8().constData());
return;
}
const QString label = fields[SavedAddressesModel::HeaderColumns::LABEL];
const QString description = fields[SavedAddressesModel::HeaderColumns::DESCRIPTION];
const SavedAddressesModel::SavedAddress importedAddress = {address, label, description};
addRow(importedAddress);
}
void SavedAddressesModel::clear()
{
beginResetModel();
m_savedAddresses.clear();
endResetModel();
}

View File

@ -20,7 +20,7 @@ public:
QString description;
};
enum HeaderColumns : int
enum HeaderColumns: int
{
ADDRESS = 0,
LABEL,
@ -45,6 +45,8 @@ public:
void addRow(SavedAddress addresstoSave);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
void loadSavedAddressFromFieldList(QStringList fields);
void clear();
private:
DebugInterface& m_cpu;

View File

@ -106,6 +106,7 @@
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
<ClCompile Include="Debugger\RegisterWidget.cpp" />
<ClCompile Include="Debugger\BreakpointDialog.cpp" />
<ClCompile Include="Debugger\DebuggerSettingsManager.cpp" />
<ClCompile Include="Debugger\Models\BreakpointModel.cpp" />
<ClCompile Include="Debugger\Models\ThreadModel.cpp" />
<ClCompile Include="Debugger\Models\StackModel.cpp" />
@ -196,6 +197,7 @@
<QtMoc Include="Debugger\MemoryViewWidget.h" />
<QtMoc Include="Debugger\RegisterWidget.h" />
<QtMoc Include="Debugger\BreakpointDialog.h" />
<ClInclude Include="Debugger\DebuggerSettingsManager.h" />
<QtMoc Include="Debugger\Models\BreakpointModel.h" />
<QtMoc Include="Debugger\Models\ThreadModel.h" />
<QtMoc Include="Debugger\Models\StackModel.h" />

View File

@ -346,6 +346,9 @@
<ClCompile Include="$(IntDir)moc_LogWindow.cpp">
<Filter>moc</Filter>
</ClCompile>
<ClCompile Include="Debugger\DebuggerSettingsManager.cpp">
<Filter>Debugger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Manifest Include="..\pcsx2\windows\PCSX2.manifest">
@ -365,6 +368,9 @@
<ClInclude Include="Settings\MemoryCardConvertWorker.h">
<Filter>Settings</Filter>
</ClInclude>
<ClInclude Include="Debugger\DebuggerSettingsManager.h">
<Filter>Debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<QtMoc Include="MainWindow.h" />

View File

@ -1210,6 +1210,7 @@ namespace EmuFolders
extern std::string AppRoot;
extern std::string DataRoot;
extern std::string Settings;
extern std::string DebuggerSettings;
extern std::string Bios;
extern std::string Snapshots;
extern std::string Savestates;

View File

@ -150,6 +150,7 @@ namespace EmuFolders
std::string AppRoot;
std::string DataRoot;
std::string Settings;
std::string DebuggerSettings;
std::string Bios;
std::string Snapshots;
std::string Savestates;
@ -2004,6 +2005,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
Textures = LoadPathFromSettings(si, DataRoot, "Textures", "textures");
InputProfiles = LoadPathFromSettings(si, DataRoot, "InputProfiles", "inputprofiles");
Videos = LoadPathFromSettings(si, DataRoot, "Videos", "videos");
DebuggerSettings = LoadPathFromSettings(si, Settings, "DebuggerSettings", "debuggersettings");
Console.WriteLn("BIOS Directory: %s", Bios.c_str());
Console.WriteLn("Snapshots Directory: %s", Snapshots.c_str());
@ -2020,6 +2022,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
Console.WriteLn("Textures Directory: %s", Textures.c_str());
Console.WriteLn("Input Profile Directory: %s", InputProfiles.c_str());
Console.WriteLn("Video Dumping Directory: %s", Videos.c_str());
Console.WriteLn("Debugger Settings Directory: %s", DebuggerSettings.c_str());
}
bool EmuFolders::EnsureFoldersExist()
@ -2035,6 +2038,7 @@ bool EmuFolders::EnsureFoldersExist()
result = FileSystem::CreateDirectoryPath(Covers.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(GameSettings.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(UserResources.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(DebuggerSettings.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(Cache.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(Textures.c_str(), false) && result;
result = FileSystem::CreateDirectoryPath(InputProfiles.c_str(), false) && result;

View File

@ -729,6 +729,22 @@ std::string VMManager::GetInputProfilePath(const std::string_view& name)
return Path::Combine(EmuFolders::InputProfiles, fmt::format("{}.ini", name));
}
std::string VMManager::GetDebuggerSettingsFilePath(const std::string_view& game_serial, u32 game_crc)
{
std::string path;
if (!game_serial.empty() && game_crc != 0)
{
auto lock = Host::GetSettingsLock();
return Path::Combine(EmuFolders::DebuggerSettings, fmt::format("{}_{:08X}.json", game_serial, game_crc));
}
return path;
}
std::string VMManager::GetDebuggerSettingsFilePathForCurrentGame()
{
return GetDebuggerSettingsFilePath(s_disc_serial, s_current_crc);
}
void VMManager::Internal::UpdateEmuFolders()
{
const std::string old_cheats_directory(EmuFolders::Cheats);

View File

@ -196,6 +196,12 @@ namespace VMManager
/// Returns the path for the input profile ini file with the specified name (may not exist).
std::string GetInputProfilePath(const std::string_view& name);
/// Returns the path for the debugger settings json file for the specified game serial and CRC.
std::string GetDebuggerSettingsFilePath(const std::string_view& game_serial, u32 game_crc);
/// Returns the path for the debugger settings json file for the current game.
std::string GetDebuggerSettingsFilePathForCurrentGame();
/// Resizes the render window to the display size, with an optional scale.
/// If the scale is set to 0, the internal resolution will be used, otherwise it is treated as a multiplier to 1x.
void RequestDisplaySize(float scale = 0.0f);