mirror of https://github.com/mgba-emu/mgba.git
Qt: Memory range dumping (closes #1298)
This commit is contained in:
parent
c076878495
commit
81fd7e3c1a
1
CHANGES
1
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
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue