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 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(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 + +namespace QGBA { + +class CoreController; + +class MemoryDump : public QDialog { +Q_OBJECT + +public: + MemoryDump(std::shared_ptr 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 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 @@ + + + MemoryDump + + + + 0 + 0 + 448 + 208 + + + + Save Memory Range + + + + + + Start Address: + + + + + + + + + -1 + + + 511 + + + 16 + + + + + + + : + + + + + + + + 0 + 0 + + + + true + + + 0x + + + 268435455 + + + 16 + + + 16 + + + + + + + + + Byte Count: + + + + + + + + 0 + 0 + + + + 0x + + + 1 + + + 268435456 + + + 256 + + + 16 + + + + + + + Dump across banks + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + buttonBox + accepted() + MemoryDump + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MemoryDump + reject() + + + 316 + 260 + + + 286 + 274 + + + + + 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 @@ -44,6 +45,7 @@ MemoryView::MemoryView(std::shared_ptr controller, QWidget* pare connect(m_ui.setAddress, static_cast(&QSpinBox::valueChanged), this, static_cast(&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 @@ 0 0 - 822 - 886 + 874 + 900 @@ -52,6 +52,13 @@ + + + + : + + + @@ -271,6 +278,13 @@ + + + + Save Range + + +