Qt: Memory range dumping (closes #1298)

This commit is contained in:
Vicki Pfau 2019-09-22 20:21:04 -07:00
parent c076878495
commit 81fd7e3c1a
9 changed files with 352 additions and 5 deletions

View File

@ -21,6 +21,7 @@ Features:
- Qt: Add export button for tile view (closes mgba.io/i/1507) - Qt: Add export button for tile view (closes mgba.io/i/1507)
- Qt: Add recent game list clearing (closes mgba.io/i/1380) - Qt: Add recent game list clearing (closes mgba.io/i/1380)
- GB: Yanking gamepak now supported - GB: Yanking gamepak now supported
- Qt: Memory range dumping (closes mgba.io/i/1298)
Emulation fixes: Emulation fixes:
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208) - GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
- GBA: Reset now reloads multiboot ROMs - GBA: Reset now reloads multiboot ROMs

View File

@ -40,7 +40,7 @@ static const struct mCoreChannelInfo _GBAudioChannels[] = {
static const struct mCoreMemoryBlock _GBMemoryBlocks[] = { static const struct mCoreMemoryBlock _GBMemoryBlocks[] = {
{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, { -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_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_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 }, { 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[] = { static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
{ -1, "mem", "All", "All", 0, 0x10000, 0x10000, mCORE_MEMORY_VIRTUAL }, { -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_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_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_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_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 }, { 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 },

View File

@ -85,6 +85,7 @@ set(SOURCE_FILES
LogConfigModel.cpp LogConfigModel.cpp
LogView.cpp LogView.cpp
MapView.cpp MapView.cpp
MemoryDump.cpp
MemoryModel.cpp MemoryModel.cpp
MemorySearch.cpp MemorySearch.cpp
MemoryView.cpp MemoryView.cpp
@ -126,6 +127,7 @@ set(UI_FILES
LoadSaveState.ui LoadSaveState.ui
LogView.ui LogView.ui
MapView.ui MapView.ui
MemoryDump.ui
MemorySearch.ui MemorySearch.ui
MemoryView.ui MemoryView.ui
ObjView.ui ObjView.ui

View File

@ -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;
}

View File

@ -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;
};
}

View File

@ -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>

View File

@ -7,6 +7,7 @@
#include "MemoryView.h" #include "MemoryView.h"
#include "CoreController.h" #include "CoreController.h"
#include "MemoryDump.h"
#include <mgba/core/core.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), connect(m_ui.setAddress, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, static_cast<void (MemoryView::*)(uint32_t)>(&MemoryView::jumpToAddress)); this, static_cast<void (MemoryView::*)(uint32_t)>(&MemoryView::jumpToAddress));
connect(m_ui.hexfield, &MemoryModel::selectionChanged, this, &MemoryView::updateSelection); 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); 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_region = qMakePair(info.start, info.end);
m_ui.segments->setValue(-1); m_ui.segments->setValue(-1);
m_ui.segments->setVisible(info.maxSegment > 0); m_ui.segments->setVisible(info.maxSegment > 0);
m_ui.segmentColon->setVisible(info.maxSegment > 0);
m_ui.segments->setMaximum(info.maxSegment); m_ui.segments->setMaximum(info.maxSegment);
m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName); m_ui.hexfield->setRegion(info.start, info.end - info.start, info.shortName);
} }
@ -142,3 +145,12 @@ void MemoryView::updateStatus() {
break; 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();
}

View File

@ -28,6 +28,7 @@ private slots:
void setSegment(int); void setSegment(int);
void updateSelection(uint32_t start, uint32_t end); void updateSelection(uint32_t start, uint32_t end);
void updateStatus(); void updateStatus();
void saveRange();
private: private:
Ui::MemoryView m_ui; Ui::MemoryView m_ui;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>822</width> <width>874</width>
<height>886</height> <height>900</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -52,6 +52,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="segmentColon">
<property name="text">
<string>:</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QSpinBox" name="setAddress"> <widget class="QSpinBox" name="setAddress">
<property name="accelerated"> <property name="accelerated">
@ -271,6 +278,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="saveRange">
<property name="text">
<string>Save Range</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="load"> <widget class="QPushButton" name="load">
<property name="text"> <property name="text">