all repos — mgba @ 6b4e37a4c6476dbff611b24d7a4ec03c8f438387

mGBA Game Boy Advance Emulator

src/platform/qt/IOViewer.cpp (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "IOViewer.h"
  7
  8#include "GameController.h"
  9
 10#include <QFontDatabase>
 11#include <QVBoxLayout>
 12
 13extern "C" {
 14#include "gba/io.h"
 15}
 16
 17using namespace QGBA;
 18
 19
 20QList<IOViewer::RegisterDescription> IOViewer::s_registers;
 21
 22const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions() {
 23	if (!s_registers.isEmpty()) {
 24		return s_registers;
 25	}
 26	// 0x04000000: DISPCNT
 27	s_registers.append({
 28		{ tr("Background mode"), 0, 3 },
 29		{ tr("CGB Mode"), 3, 1, true },
 30		{ tr("Frame select"), 4 },
 31		{ tr("Unlocked HBlank"), 5 },
 32		{ tr("Linear OBJ tile mapping"), 6 },
 33		{ tr("Force blank screen"), 7 },
 34		{ tr("Enable background 0"), 8 },
 35		{ tr("Enable background 1"), 9 },
 36		{ tr("Enable background 2"), 10 },
 37		{ tr("Enable background 3"), 11 },
 38		{ tr("Enable OBJ"), 12 },
 39		{ tr("Enable Window 0"), 13 },
 40		{ tr("Enable Window 1"), 14 },
 41		{ tr("Enable OBJ Window"), 15 },
 42	});
 43	// 0x04000002: Green swap (undocumented and unimplemented)
 44	s_registers.append(RegisterDescription());
 45	// 0x04000004: DISPSTAT
 46	s_registers.append({
 47		{ tr("Currently in VBlank"), 0, 1, true },
 48		{ tr("Currently in HBlank"), 1, 1, true },
 49		{ tr("Currently in VCounter"), 2, 1, true },
 50		{ tr("Enable VBlank IRQ generation"), 3 },
 51		{ tr("Enable HBlank IRQ generation"), 4 },
 52		{ tr("Enable VCounter IRQ generation"), 5 },
 53		{ tr("VCounter scanline"), 8, 8 },
 54	});
 55	// 0x04000006: VCOUNT
 56	s_registers.append({
 57		{ tr("Current scanline"), 0, 8, true },
 58	});
 59	// 0x04000008: BG0CNT
 60	s_registers.append({
 61		{ tr("Priority"), 0, 2 },
 62		{ tr("Tile data base (* 16kB)"), 2, 2 },
 63		{ tr("Enable mosaic"), 3 },
 64		{ tr("Enable 256-color"), 3 },
 65		{ tr("Tile map base (* 2kB)"), 8, 5 },
 66		{ tr("Background dimensions"), 14, 2 },
 67	});
 68	// 0x0400000A: BG1CNT
 69	s_registers.append({
 70		{ tr("Priority"), 0, 2 },
 71		{ tr("Tile data base (* 16kB)"), 2, 2 },
 72		{ tr("Enable mosaic"), 3 },
 73		{ tr("Enable 256-color"), 3 },
 74		{ tr("Tile map base (* 2kB)"), 8, 5 },
 75		{ tr("Background dimensions"), 14, 2 },
 76	});
 77	// 0x0400000C: BG2CNT
 78	s_registers.append({
 79		{ tr("Priority"), 0, 2 },
 80		{ tr("Tile data base (* 16kB)"), 2, 2 },
 81		{ tr("Enable mosaic"), 3 },
 82		{ tr("Enable 256-color"), 3 },
 83		{ tr("Tile map base (* 2kB)"), 8, 5 },
 84		{ tr("Overflow wraps"), 9 },
 85		{ tr("Background dimensions"), 14, 2 },
 86	});
 87	// 0x0400000E: BG3CNT
 88	s_registers.append({
 89		{ tr("Priority"), 0, 2 },
 90		{ tr("Tile data base (* 16kB)"), 2, 2 },
 91		{ tr("Enable mosaic"), 3 },
 92		{ tr("Enable 256-color"), 3 },
 93		{ tr("Tile map base (* 2kB)"), 8, 5 },
 94		{ tr("Overflow wraps"), 9 },
 95		{ tr("Background dimensions"), 14, 2 },
 96	});
 97	// 0x04000010: BG0HOFS
 98	s_registers.append({
 99		{ tr("Horizontal offset"), 0, 9 },
100	});
101	// 0x04000012: BG0VOFS
102	s_registers.append({
103		{ tr("Vertical offset"), 0, 9 },
104	});
105	// 0x04000014: BG1HOFS
106	s_registers.append({
107		{ tr("Horizontal offset"), 0, 9 },
108	});
109	// 0x04000016: BG1VOFS
110	s_registers.append({
111		{ tr("Vertical offset"), 0, 9 },
112	});
113	// 0x04000018: BG2HOFS
114	s_registers.append({
115		{ tr("Horizontal offset"), 0, 9 },
116	});
117	// 0x0400001A: BG2VOFS
118	s_registers.append({
119		{ tr("Vertical offset"), 0, 9 },
120	});
121	// 0x0400001C: BG3HOFS
122	s_registers.append({
123		{ tr("Horizontal offset"), 0, 9 },
124	});
125	// 0x0400001E: BG3VOFS
126	s_registers.append({
127		{ tr("Vertical offset"), 0, 9 },
128	});
129	return s_registers;
130}
131
132IOViewer::IOViewer(GameController* controller, QWidget* parent)
133	: QDialog(parent)
134	, m_controller(controller)
135{
136	m_ui.setupUi(this);
137
138	for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
139		const char* reg = GBAIORegisterNames[i];
140		if (!reg) {
141			continue;
142		}
143		m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
144	}
145
146	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
147	m_ui.regValue->setFont(font);
148
149	connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
150	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
151	connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
152
153	m_b[0] = m_ui.b0;
154	m_b[1] = m_ui.b1;
155	m_b[2] = m_ui.b2;
156	m_b[3] = m_ui.b3;
157	m_b[4] = m_ui.b4;
158	m_b[5] = m_ui.b5;
159	m_b[6] = m_ui.b6;
160	m_b[7] = m_ui.b7;
161	m_b[8] = m_ui.b8;
162	m_b[9] = m_ui.b9;
163	m_b[10] = m_ui.bA;
164	m_b[11] = m_ui.bB;
165	m_b[12] = m_ui.bC;
166	m_b[13] = m_ui.bD;
167	m_b[14] = m_ui.bE;
168	m_b[15] = m_ui.bF;
169
170	for (int i = 0; i < 16; ++i) {
171		connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
172	}
173
174	selectRegister(0);
175}
176
177void IOViewer::updateRegister() {
178	m_value = 0;
179	uint16_t value = 0;
180	m_controller->threadInterrupt();
181	if (m_controller->isLoaded()) {
182		value = GBAIORead(m_controller->thread()->gba, m_register);
183	}
184	m_controller->threadContinue();
185
186	for (int i = 0; i < 16; ++i) {
187		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
188	}
189	m_value = value;
190}
191
192void IOViewer::bitFlipped() {
193	m_value = 0;
194	for (int i = 0; i < 16; ++i) {
195		m_value |= m_b[i]->isChecked() << i;
196	}
197	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
198}
199
200void IOViewer::writeback() {
201	m_controller->threadInterrupt();
202	if (m_controller->isLoaded()) {
203		GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
204	}
205	m_controller->threadContinue();
206	updateRegister();
207}
208
209void IOViewer::selectRegister(unsigned address) {
210	m_register = address;
211	QLayout* box = m_ui.regDescription->layout();
212	if (box) {
213		// I can't believe there isn't a real way to do this...
214		while (!box->isEmpty()) {
215			QLayoutItem* item = box->takeAt(0);
216			if (item->widget()) {
217				delete item->widget();
218			}
219			delete item;
220		}
221	} else {
222		box = new QVBoxLayout;
223	}
224	if (registerDescriptions().count() > address >> 1) {
225		// TODO: Remove the check when done filling in register information
226		const RegisterDescription& description = registerDescriptions().at(address >> 1);
227		for (const RegisterItem& ri : description) {
228			QCheckBox* check = new QCheckBox;
229			check->setText(ri.description);
230			check->setEnabled(!ri.readonly);
231			box->addWidget(check);
232			connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
233			connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
234		}
235	}
236	m_ui.regDescription->setLayout(box);
237	updateRegister();
238}
239
240void IOViewer::selectRegister() {
241	selectRegister(m_ui.regSelect->currentData().toUInt());
242}
243
244void IOViewer::buttonPressed(QAbstractButton* button) {
245	switch (m_ui.buttonBox->standardButton(button)) {
246	case QDialogButtonBox::Reset:
247		updateRegister();
248	 	break;
249	case QDialogButtonBox::Apply:
250	 	writeback();
251	 	break;
252	default:
253		break;
254	}
255}