From 81fd7e3c1a811e0dea0c8995143c4c7f899fd672 Mon Sep 17 00:00:00 2001 From: Vicki Pfau <vi@endrift.com> Date: Sun, 22 Sep 2019 20:21:04 -0700 Subject: [PATCH] Qt: Memory range dumping (closes #1298) --- CHANGES | 1 + src/gb/core.c | 6 +- src/platform/qt/CMakeLists.txt | 2 + src/platform/qt/MemoryDump.cpp | 120 +++++++++++++++++++++++++ src/platform/qt/MemoryDump.h | 38 ++++++++ src/platform/qt/MemoryDump.ui | 159 +++++++++++++++++++++++++++++++++ src/platform/qt/MemoryView.cpp | 12 +++ src/platform/qt/MemoryView.h | 1 + src/platform/qt/MemoryView.ui | 18 +++- 9 files changed, 352 insertions(+), 5 deletions(-) create mode 100644 src/platform/qt/MemoryDump.cpp create mode 100644 src/platform/qt/MemoryDump.h create mode 100644 src/platform/qt/MemoryDump.ui diff --git a/CHANGES b/CHANGES index 78f983c68..3ed7e3302 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Features: - Qt: Add export button for tile view (closes mgba.io/i/1507) - Qt: Add recent game list clearing (closes mgba.io/i/1380) - GB: Yanking gamepak now supported + - Qt: Memory range dumping (closes mgba.io/i/1298) Emulation fixes: - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: Reset now reloads multiboot ROMs diff --git a/src/gb/core.c b/src/gb/core.c index 655449eaf..25d356143 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -40,7 +40,7 @@ static const struct mCoreChannelInfo _GBAudioChannels[] = { static const struct mCoreMemoryBlock _GBMemoryBlocks[] = { { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, - { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 }, { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2 , GB_SIZE_WORKING_RAM_BANK0 * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, @@ -51,10 +51,10 @@ static const struct mCoreMemoryBlock _GBMemoryBlocks[] = { static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = { { -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, - { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511 }, + { GB_REGION_CART_BANK0, "cart0", "ROM Bank", "Game Pak (32kiB)", GB_BASE_CART_BANK0, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 * 2, 0x800000, mCORE_MEMORY_READ | mCORE_MEMORY_MAPPED, 511, GB_BASE_CART_BANK0 + GB_SIZE_CART_BANK0 }, { GB_REGION_VRAM, "vram", "VRAM", "Video RAM (8kiB)", GB_BASE_VRAM, GB_BASE_VRAM + GB_SIZE_VRAM, GB_SIZE_VRAM * 2, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 }, { GB_REGION_EXTERNAL_RAM, "sram", "SRAM", "External RAM (8kiB)", GB_BASE_EXTERNAL_RAM, GB_BASE_EXTERNAL_RAM + GB_SIZE_EXTERNAL_RAM, GB_SIZE_EXTERNAL_RAM * 4, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 3 }, - { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7 }, + { GB_REGION_WORKING_RAM_BANK0, "wram", "WRAM", "Working RAM (8kiB)", GB_BASE_WORKING_RAM_BANK0, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 * 2, GB_SIZE_WORKING_RAM_BANK0 * 8, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 7, GB_BASE_WORKING_RAM_BANK0 + GB_SIZE_WORKING_RAM_BANK0 }, { GB_BASE_OAM, "oam", "OAM", "OBJ Attribute Memory", GB_BASE_OAM, GB_BASE_OAM + GB_SIZE_OAM, GB_SIZE_OAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, { GB_BASE_IO, "io", "MMIO", "Memory-Mapped I/O", GB_BASE_IO, GB_BASE_IO + GB_SIZE_IO, GB_SIZE_IO, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 32fa95842..0c681c75c 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -85,6 +85,7 @@ set(SOURCE_FILES LogConfigModel.cpp LogView.cpp MapView.cpp + MemoryDump.cpp MemoryModel.cpp MemorySearch.cpp MemoryView.cpp @@ -126,6 +127,7 @@ set(UI_FILES LoadSaveState.ui LogView.ui MapView.ui + MemoryDump.ui MemorySearch.ui MemoryView.ui ObjView.ui diff --git a/src/platform/qt/MemoryDump.cpp b/src/platform/qt/MemoryDump.cpp new file mode 100644 index 000000000..982353ee2 --- /dev/null +++ b/src/platform/qt/MemoryDump.cpp @@ -0,0 +1,120 @@ +/* Copyright (c) 2013-2019 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 "MemoryDump.h" + +#include "CoreController.h" +#include "GBAApp.h" +#include "LogController.h" + +using namespace QGBA; + +MemoryDump::MemoryDump(std::shared_ptr<CoreController> controller, QWidget* parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) + , m_controller(controller) +{ + m_ui.setupUi(this); + + connect(this, &QDialog::accepted, this, &MemoryDump::save); +} + +void MemoryDump::save() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Save memory region")); + if (filename.isNull()) { + return; + } + QFile outfile(filename); + if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + LOG(QT, WARN) << tr("Failed to open output file: %1").arg(filename); + return; + } + QByteArray out(serialize()); + outfile.write(out); +} + +void MemoryDump::setAddress(uint32_t start) { + m_ui.address->setValue(start); +} + +void MemoryDump::setSegment(int seg) { + m_ui.segment->setValue(seg); +} + +void MemoryDump::setByteCount(uint32_t count) { + m_ui.bytes->setValue(count); +} + +QByteArray MemoryDump::serialize() { + CoreController::Interrupter interrupter(m_controller); + mCore* core = m_controller->thread()->core; + const mCoreMemoryBlock* blocks; + size_t nBlocks = core->listMemoryBlocks(core, &blocks); + + int size = m_ui.bytes->value(); + uint32_t start = m_ui.address->value(); + int segment = m_ui.segment->value(); + bool spanSegments = m_ui.spanSegments->isChecked(); + + QByteArray mem; + while (size) { + const mCoreMemoryBlock* bestMatch = NULL; + const char* block = NULL; + size_t blockSize = 0; + for (size_t i = 0; i < nBlocks; ++i) { + if (blocks[i].end <= start) { + continue; + } + if (blocks[i].start > start) { + continue; + } + block = static_cast<const char*>(core->getMemoryBlock(core, blocks[i].id, &blockSize)); + if (block) { + bestMatch = &blocks[i]; + break; + } else if (!bestMatch) { + bestMatch = &blocks[i]; + blockSize = 0; + } + } + if (!spanSegments) { + blockSize = bestMatch->end - bestMatch->start; + } else if (!blockSize) { + blockSize = bestMatch->size; + } + size_t segmentSize = bestMatch->end - bestMatch->start; + if (bestMatch->segmentStart) { + segmentSize = bestMatch->segmentStart - bestMatch->start; + } + if (segment > 0) { + start += segment * segmentSize; + } + int maxFromRegion = blockSize - (start - bestMatch->start); + if (maxFromRegion <= 0) { + continue; + } + int fromRegion = std::min(size, maxFromRegion); + if (block && (segment >= 0 || segmentSize == blockSize)) { + block = &block[start - bestMatch->start]; + mem.append(QByteArray::fromRawData(block, fromRegion)); + size -= fromRegion; + start += fromRegion; + if (spanSegments) { + break; + } + } else { + for (int i = 0; i < 16; ++i) { + char datum = core->rawRead8(core, start, segment); + mem.append(datum); + ++start; + --size; + if (!size) { + break; + } + } + } + } + + return mem; +} \ No newline at end of file diff --git a/src/platform/qt/MemoryDump.h b/src/platform/qt/MemoryDump.h new file mode 100644 index 000000000..bfed5fe08 --- /dev/null +++ b/src/platform/qt/MemoryDump.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2013-2019 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 "ui_MemoryDump.h" + +#include <memory> + +namespace QGBA { + +class CoreController; + +class MemoryDump : public QDialog { +Q_OBJECT + +public: + MemoryDump(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr); + +public slots: + void setSegment(int); + void setAddress(uint32_t address); + void setByteCount(uint32_t); + +private slots: + void save(); + +private: + QByteArray serialize(); + + Ui::MemoryDump m_ui; + + std::shared_ptr<CoreController> m_controller; +}; + +} diff --git a/src/platform/qt/MemoryDump.ui b/src/platform/qt/MemoryDump.ui new file mode 100644 index 000000000..89915347e --- /dev/null +++ b/src/platform/qt/MemoryDump.ui @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MemoryDump</class> + <widget class="QDialog" name="MemoryDump"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>448</width> + <height>208</height> + </rect> + </property> + <property name="windowTitle"> + <string>Save Memory Range</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Start Address:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QSpinBox" name="segment"> + <property name="minimum"> + <number>-1</number> + </property> + <property name="maximum"> + <number>511</number> + </property> + <property name="displayIntegerBase"> + <number>16</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>:</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="address"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="accelerated"> + <bool>true</bool> + </property> + <property name="prefix"> + <string>0x</string> + </property> + <property name="maximum"> + <number>268435455</number> + </property> + <property name="singleStep"> + <number>16</number> + </property> + <property name="displayIntegerBase"> + <number>16</number> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Byte Count:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="bytes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="prefix"> + <string>0x</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>268435456</number> + </property> + <property name="value"> + <number>256</number> + </property> + <property name="displayIntegerBase"> + <number>16</number> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QCheckBox" name="spanSegments"> + <property name="text"> + <string>Dump across banks</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>MemoryDump</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>MemoryDump</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/platform/qt/MemoryView.cpp b/src/platform/qt/MemoryView.cpp index 4d6827326..0aefb642f 100644 --- a/src/platform/qt/MemoryView.cpp +++ b/src/platform/qt/MemoryView.cpp @@ -7,6 +7,7 @@ #include "MemoryView.h" #include "CoreController.h" +#include "MemoryDump.h" #include <mgba/core/core.h> @@ -44,6 +45,7 @@ MemoryView::MemoryView(std::shared_ptr<CoreController> controller, QWidget* pare connect(m_ui.setAddress, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, static_cast<void (MemoryView::*)(uint32_t)>(&MemoryView::jumpToAddress)); connect(m_ui.hexfield, &MemoryModel::selectionChanged, this, &MemoryView::updateSelection); + connect(m_ui.saveRange, &QAbstractButton::clicked, this, &MemoryView::saveRange); connect(controller.get(), &CoreController::stopping, this, &QWidget::close); @@ -69,6 +71,7 @@ void MemoryView::setIndex(int index) { m_region = qMakePair(info.start, info.end); m_ui.segments->setValue(-1); m_ui.segments->setVisible(info.maxSegment > 0); + m_ui.segmentColon->setVisible(info.maxSegment > 0); m_ui.segments->setMaximum(info.maxSegment); m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName); } @@ -142,3 +145,12 @@ void MemoryView::updateStatus() { break; } } + +void MemoryView::saveRange() { + MemoryDump* memdump = new MemoryDump(m_controller); + memdump->setAttribute(Qt::WA_DeleteOnClose); + memdump->setAddress(m_selection.first); + memdump->setSegment(m_ui.segments->value()); + memdump->setByteCount(m_selection.second - m_selection.first); + memdump->show(); +} \ No newline at end of file diff --git a/src/platform/qt/MemoryView.h b/src/platform/qt/MemoryView.h index bc276af35..ac4bfe53d 100644 --- a/src/platform/qt/MemoryView.h +++ b/src/platform/qt/MemoryView.h @@ -28,6 +28,7 @@ private slots: void setSegment(int); void updateSelection(uint32_t start, uint32_t end); void updateStatus(); + void saveRange(); private: Ui::MemoryView m_ui; diff --git a/src/platform/qt/MemoryView.ui b/src/platform/qt/MemoryView.ui index b00c710f8..fb43fbe22 100644 --- a/src/platform/qt/MemoryView.ui +++ b/src/platform/qt/MemoryView.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>822</width> - <height>886</height> + <width>874</width> + <height>900</height> </rect> </property> <property name="windowTitle"> @@ -52,6 +52,13 @@ </property> </widget> </item> + <item> + <widget class="QLabel" name="segmentColon"> + <property name="text"> + <string>:</string> + </property> + </widget> + </item> <item> <widget class="QSpinBox" name="setAddress"> <property name="accelerated"> @@ -271,6 +278,13 @@ </property> </widget> </item> + <item> + <widget class="QPushButton" name="saveRange"> + <property name="text"> + <string>Save Range</string> + </property> + </widget> + </item> <item> <widget class="QPushButton" name="load"> <property name="text">