diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 3122c70e0..910ab9b57 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -261,6 +261,7 @@ if(ENABLE_DEBUGGERS) DebuggerController.cpp DebuggerConsole.cpp DebuggerConsoleController.cpp + MemoryAccessLogController.cpp MemoryAccessLogView.cpp) endif() diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 2496f8039..152efc222 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -8,6 +8,7 @@ #include "ConfigController.h" #include "InputController.h" #include "LogController.h" +#include "MemoryAccessLogController.h" #include "MultiplayerController.h" #include "Override.h" @@ -459,6 +460,15 @@ void CoreController::setLogger(LogController* logger) { connect(this, &CoreController::logPosted, m_log, &LogController::postLog); } +#ifdef ENABLE_DEBUGGERS +std::weak_ptr CoreController::memoryAccessLogController() { + if (!m_malController) { + m_malController = std::make_shared(this); + } + return m_malController; +} +#endif + void CoreController::start() { QSize size(screenDimensions()); m_activeBuffer.resize(size.width() * size.height() * sizeof(mColor)); @@ -479,6 +489,10 @@ void CoreController::start() { void CoreController::stop() { setSync(false); #ifdef ENABLE_DEBUGGERS + if (m_malController) { + m_malController->stop(); + } + detachDebugger(); #endif setPaused(false); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 57eb9ffe0..a2ebe410e 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -40,6 +40,7 @@ namespace QGBA { class ConfigController; class InputController; class LogController; +class MemoryAccessLogController; class MultiplayerController; class Override; @@ -113,6 +114,8 @@ public: void detachDebugger(); void attachDebuggerModule(mDebuggerModule*, bool interrupt = true); void detachDebuggerModule(mDebuggerModule*); + + std::weak_ptr memoryAccessLogController(); #endif void setMultiplayerController(MultiplayerController*); @@ -326,6 +329,10 @@ private: GBASIODolphin m_dolphin; #endif +#ifdef ENABLE_DEBUGGERS + std::shared_ptr m_malController; +#endif + mVideoLogContext* m_vl = nullptr; VFile* m_vlVf = nullptr; diff --git a/src/platform/qt/MemoryAccessLogController.cpp b/src/platform/qt/MemoryAccessLogController.cpp new file mode 100644 index 000000000..95200e53e --- /dev/null +++ b/src/platform/qt/MemoryAccessLogController.cpp @@ -0,0 +1,118 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MemoryAccessLogController.h" + +#include "GBAApp.h" +#include "LogController.h" +#include "utils.h" +#include "VFileDevice.h" + +using namespace QGBA; + +MemoryAccessLogController::MemoryAccessLogController(CoreController* controller, QObject* parent) + : QObject(parent) + , m_controller(controller) +{ + mCore* core = m_controller->thread()->core; + const mCoreMemoryBlock* info; + size_t nBlocks = core->listMemoryBlocks(core, &info); + + for (size_t i = 0; i < nBlocks; ++i) { + if (!(info[i].flags & mCORE_MEMORY_MAPPED)) { + continue; + } + m_regions.append({ + QString::fromUtf8(info[i].longName), + QString::fromUtf8(info[i].internalName) + }); + } +} + +MemoryAccessLogController::~MemoryAccessLogController() { + stop(); +} + +bool MemoryAccessLogController::canExport() const { + return m_regionMapping.contains("cart0"); +} + +void MemoryAccessLogController::updateRegion(const QString& internalName, bool checked) { + if (checked) { + m_watchedRegions += internalName; + } else { + m_watchedRegions -= internalName; + } + if (!m_active) { + return; + } + m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags()); + emit regionMappingChanged(internalName, checked); +} + +void MemoryAccessLogController::setFile(const QString& path) { + m_path = path; +} + +void MemoryAccessLogController::start(bool loadExisting, bool logExtra) { + int flags = O_CREAT | O_RDWR; + if (!loadExisting) { + flags |= O_TRUNC; + } + VFile* vf = VFileDevice::open(m_path, flags); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open memory log file"); + return; + } + m_logExtra = logExtra; + + mDebuggerAccessLoggerInit(&m_logger); + CoreController::Interrupter interrupter(m_controller); + m_controller->attachDebuggerModule(&m_logger.d); + if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) { + mDebuggerAccessLoggerDeinit(&m_logger); + LOG(QT, ERROR) << tr("Failed to open memory log file"); + return; + } + + m_active = true; + emit loggingChanged(true); + for (const auto& region : m_watchedRegions) { + m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags()); + } + interrupter.resume(); +} + +void MemoryAccessLogController::stop() { + if (!m_active) { + return; + } + CoreController::Interrupter interrupter(m_controller); + m_controller->detachDebuggerModule(&m_logger.d); + mDebuggerAccessLoggerDeinit(&m_logger); + emit loggingChanged(false); + interrupter.resume(); + m_active = false; +} + +mDebuggerAccessLogRegionFlags MemoryAccessLogController::activeFlags() const { + mDebuggerAccessLogRegionFlags loggerFlags = 0; + if (m_logExtra) { + loggerFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(loggerFlags); + } + return loggerFlags; +} + +void MemoryAccessLogController::exportFile(const QString& filename) { + VFile* vf = VFileDevice::open(filename, O_CREAT | O_TRUNC | O_WRONLY); + if (!vf) { + // log error + return; + } + + CoreController::Interrupter interrupter(m_controller); + mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0); + vf->close(vf); +} diff --git a/src/platform/qt/MemoryAccessLogController.h b/src/platform/qt/MemoryAccessLogController.h new file mode 100644 index 000000000..42ef655c3 --- /dev/null +++ b/src/platform/qt/MemoryAccessLogController.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2013-2025 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include + +#include + +#include "CoreController.h" + +#include + +namespace QGBA { + +class MemoryAccessLogController : public QObject { +Q_OBJECT + +public: + struct Region { + QString longName; + QString internalName; + }; + + MemoryAccessLogController(CoreController* controller, QObject* parent = nullptr); + ~MemoryAccessLogController(); + + QVector listRegions() const { return m_regions; } + QSet watchedRegions() const { return m_watchedRegions; } + + bool canExport() const; + mPlatform platform() const { return m_controller->platform(); } + + QString file() const { return m_path; } + bool active() const { return m_active; } + +public slots: + void updateRegion(const QString& internalName, bool enable); + void setFile(const QString& path); + + void start(bool loadExisting, bool logExtra); + void stop(); + + void exportFile(const QString& filename); + +signals: + void loggingChanged(bool active); + void regionMappingChanged(const QString& internalName, bool active); + +private: + bool m_logExtra = false; + QString m_path; + CoreController* m_controller; + QSet m_watchedRegions; + QHash m_regionMapping; + QVector m_regions; + struct mDebuggerAccessLogger m_logger{}; + bool m_active = false; + + mDebuggerAccessLogRegionFlags activeFlags() const; +}; + +} diff --git a/src/platform/qt/MemoryAccessLogView.cpp b/src/platform/qt/MemoryAccessLogView.cpp index 3c846b440..97a0c932e 100644 --- a/src/platform/qt/MemoryAccessLogView.cpp +++ b/src/platform/qt/MemoryAccessLogView.cpp @@ -9,104 +9,66 @@ #include "GBAApp.h" #include "LogController.h" +#include "MemoryAccessLogController.h" #include "utils.h" #include "VFileDevice.h" using namespace QGBA; -MemoryAccessLogView::MemoryAccessLogView(std::shared_ptr controller, QWidget* parent) +MemoryAccessLogView::MemoryAccessLogView(std::weak_ptr controller, QWidget* parent) : QWidget(parent) - , m_controller(std::move(controller)) + , m_controller(controller) { m_ui.setupUi(this); + std::shared_ptr controllerPtr = m_controller.lock(); connect(m_ui.browse, &QAbstractButton::clicked, this, &MemoryAccessLogView::selectFile); connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MemoryAccessLogView::exportFile); - connect(this, &MemoryAccessLogView::loggingChanged, m_ui.start, &QWidget::setDisabled); - connect(this, &MemoryAccessLogView::loggingChanged, m_ui.stop, &QWidget::setEnabled); - connect(this, &MemoryAccessLogView::loggingChanged, m_ui.filename, &QWidget::setDisabled); - connect(this, &MemoryAccessLogView::loggingChanged, m_ui.browse, &QWidget::setDisabled); + connect(controllerPtr.get(), &MemoryAccessLogController::regionMappingChanged, this, &MemoryAccessLogView::updateRegion); + connect(controllerPtr.get(), &MemoryAccessLogController::loggingChanged, this, &MemoryAccessLogView::handleStartStop); - mCore* core = m_controller->thread()->core; - const mCoreMemoryBlock* info; - size_t nBlocks = core->listMemoryBlocks(core, &info); + bool active = controllerPtr->active(); + auto watchedRegions = controllerPtr->watchedRegions(); QVBoxLayout* regionBox = static_cast(m_ui.regionBox->layout()); - for (size_t i = 0; i < nBlocks; ++i) { - if (!(info[i].flags & mCORE_MEMORY_MAPPED)) { - continue; - } - QCheckBox* region = new QCheckBox(QString::fromUtf8(info[i].longName)); + for (const auto& info : controllerPtr->listRegions()) { + QCheckBox* region = new QCheckBox(info.longName); regionBox->addWidget(region); - QString name(QString::fromUtf8(info[i].internalName)); + QString name(info.internalName); m_regionBoxes[name] = region; connect(region, &QAbstractButton::toggled, this, [this, name](bool checked) { - updateRegion(name, checked); + std::shared_ptr controllerPtr = m_controller.lock(); + if (!controllerPtr) { + return; + } + controllerPtr->updateRegion(name, checked); }); } + + handleStartStop(active); } -MemoryAccessLogView::~MemoryAccessLogView() { - stop(); -} - -void MemoryAccessLogView::updateRegion(const QString& internalName, bool checked) { - if (checked) { - m_watchedRegions += internalName; - } else { - m_watchedRegions -= internalName; - } - if (!m_active) { - return; - } +void MemoryAccessLogView::updateRegion(const QString& internalName, bool) { m_regionBoxes[internalName]->setEnabled(false); - m_regionMapping[internalName] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, internalName.toUtf8().constData(), activeFlags()); } void MemoryAccessLogView::start() { - int flags = O_CREAT | O_RDWR; - if (!m_ui.loadExisting->isChecked()) { - flags |= O_TRUNC; - } - VFile* vf = VFileDevice::open(m_ui.filename->text(), flags); - if (!vf) { - // log error + std::shared_ptr controllerPtr = m_controller.lock(); + if (!controllerPtr) { return; } - mDebuggerAccessLoggerInit(&m_logger); - CoreController::Interrupter interrupter(m_controller); - m_controller->attachDebuggerModule(&m_logger.d); - if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) { - mDebuggerAccessLoggerDeinit(&m_logger); - LOG(QT, ERROR) << tr("Failed to open memory log file"); - return; - } - - m_active = true; - emit loggingChanged(true); - for (const auto& region : m_watchedRegions) { - m_regionBoxes[region]->setEnabled(false); - m_regionMapping[region] = mDebuggerAccessLoggerWatchMemoryBlockName(&m_logger, region.toUtf8().constData(), activeFlags()); - } - interrupter.resume(); - - if (m_watchedRegions.contains(QString("cart0"))) { - m_ui.exportButton->setEnabled(true); - } + controllerPtr->setFile(m_ui.filename->text()); + controllerPtr->start(m_ui.loadExisting->isChecked(), m_ui.logExtra->isChecked()); } void MemoryAccessLogView::stop() { - if (!m_active) { + std::shared_ptr controllerPtr = m_controller.lock(); + if (!controllerPtr) { return; } - CoreController::Interrupter interrupter(m_controller); - m_controller->detachDebuggerModule(&m_logger.d); - mDebuggerAccessLoggerDeinit(&m_logger); - emit loggingChanged(false); - interrupter.resume(); - - for (const auto& region : m_watchedRegions) { + controllerPtr->stop(); + for (const auto& region : controllerPtr->watchedRegions()) { m_regionBoxes[region]->setEnabled(true); } m_ui.exportButton->setEnabled(false); @@ -119,30 +81,41 @@ void MemoryAccessLogView::selectFile() { } } -mDebuggerAccessLogRegionFlags MemoryAccessLogView::activeFlags() const { - mDebuggerAccessLogRegionFlags loggerFlags = 0; - if (m_ui.logExtra->isChecked()) { - loggerFlags = mDebuggerAccessLogRegionFlagsFillHasExBlock(loggerFlags); - } - return loggerFlags; -} - void MemoryAccessLogView::exportFile() { - if (!m_regionMapping.contains("cart0")) { + std::shared_ptr controllerPtr = m_controller.lock(); + if (!controllerPtr) { + return; + } + if (!controllerPtr->canExport()) { return; } - QString filename = GBAApp::app()->getSaveFileName(this, tr("Select access log file"), romFilters(false, m_controller->platform(), true)); + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select access log file"), romFilters(false, controllerPtr->platform(), true)); if (filename.isEmpty()) { return; } - VFile* vf = VFileDevice::open(filename, O_CREAT | O_TRUNC | O_WRONLY); - if (!vf) { - // log error + controllerPtr->exportFile(filename); +} + +void MemoryAccessLogView::handleStartStop(bool start) { + std::shared_ptr controllerPtr = m_controller.lock(); + if (!controllerPtr) { return; } + m_ui.filename->setText(controllerPtr->file()); - CoreController::Interrupter interrupter(m_controller); - mDebuggerAccessLoggerCreateShadowFile(&m_logger, m_regionMapping[QString("cart0")], vf, 0); - vf->close(vf); + auto watchedRegions = controllerPtr->watchedRegions(); + for (const auto& region : watchedRegions) { + m_regionBoxes[region]->setDisabled(start); + m_regionBoxes[region]->setChecked(true); + } + + if (watchedRegions.contains(QString("cart0"))) { + m_ui.exportButton->setEnabled(start); + } + + m_ui.start->setDisabled(start); + m_ui.stop->setEnabled(start); + m_ui.filename->setDisabled(start); + m_ui.browse->setDisabled(start); } diff --git a/src/platform/qt/MemoryAccessLogView.h b/src/platform/qt/MemoryAccessLogView.h index ffe31cc43..e221a5d9d 100644 --- a/src/platform/qt/MemoryAccessLogView.h +++ b/src/platform/qt/MemoryAccessLogView.h @@ -18,12 +18,14 @@ namespace QGBA { +class MemoryAccessLogController; + class MemoryAccessLogView : public QWidget { Q_OBJECT public: - MemoryAccessLogView(std::shared_ptr controller, QWidget* parent = nullptr); - ~MemoryAccessLogView(); + MemoryAccessLogView(std::weak_ptr controller, QWidget* parent = nullptr); + ~MemoryAccessLogView() = default; private slots: void updateRegion(const QString& internalName, bool enable); @@ -34,20 +36,13 @@ private slots: void exportFile(); -signals: - void loggingChanged(bool active); + void handleStartStop(bool start); private: Ui::MemoryAccessLogView m_ui; - std::shared_ptr m_controller; - QSet m_watchedRegions; + std::weak_ptr m_controller; QHash m_regionBoxes; - QHash m_regionMapping; - struct mDebuggerAccessLogger m_logger{}; - bool m_active = false; - - mDebuggerAccessLogRegionFlags activeFlags() const; }; } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index bc956fcc4..1be4a998c 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1767,7 +1767,12 @@ void Window::setupMenu(QMenuBar* menubar) { addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView(), "stateViews"); #ifdef ENABLE_DEBUGGERS - addGameAction(tr("Log memory &accesses..."), "memoryAccessView", openControllerTView(), "tools"); + addGameAction(tr("Log memory &accesses..."), "memoryAccessView", [this]() { + std::weak_ptr controller = m_controller->memoryAccessLogController(); + MemoryAccessLogView* view = new MemoryAccessLogView(controller); + connect(m_controller.get(), &CoreController::stopping, view, &QWidget::close); + openView(view); + }, "tools"); #endif #if defined(USE_FFMPEG) && defined(M_CORE_GBA)