all repos — mgba @ 1706503cb96fa8219110582c5717221f5f008c46

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 <QGridLayout>
 12#include <QRadioButton>
 13#include <QSpinBox>
 14
 15extern "C" {
 16#include "gba/io.h"
 17}
 18
 19using namespace QGBA;
 20
 21
 22QList<IOViewer::RegisterDescription> IOViewer::s_registers;
 23
 24const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions() {
 25	if (!s_registers.isEmpty()) {
 26		return s_registers;
 27	}
 28	// 0x04000000: DISPCNT
 29	s_registers.append({
 30		{ tr("Background mode"), 0, 3, {
 31			tr("Mode 0: 4 tile layers"),
 32			tr("Mode 1: 2 tile layers + 1 rotated/scaled tile layer"),
 33			tr("Mode 2: 2 rotated/scaled tile layers"),
 34			tr("Mode 3: Full 15-bit bitmap"),
 35			tr("Mode 4: Full 8-bit bitmap"),
 36			tr("Mode 5: Small 15-bit bitmap"),
 37			QString(),
 38			QString()
 39		} },
 40		{ tr("CGB Mode"), 3, 1, true },
 41		{ tr("Frame select"), 4 },
 42		{ tr("Unlocked HBlank"), 5 },
 43		{ tr("Linear OBJ tile mapping"), 6 },
 44		{ tr("Force blank screen"), 7 },
 45		{ tr("Enable background 0"), 8 },
 46		{ tr("Enable background 1"), 9 },
 47		{ tr("Enable background 2"), 10 },
 48		{ tr("Enable background 3"), 11 },
 49		{ tr("Enable OBJ"), 12 },
 50		{ tr("Enable Window 0"), 13 },
 51		{ tr("Enable Window 1"), 14 },
 52		{ tr("Enable OBJ Window"), 15 },
 53	});
 54	// 0x04000002: Green swap (undocumented and unimplemented)
 55	s_registers.append(RegisterDescription());
 56	// 0x04000004: DISPSTAT
 57	s_registers.append({
 58		{ tr("Currently in VBlank"), 0, 1, true },
 59		{ tr("Currently in HBlank"), 1, 1, true },
 60		{ tr("Currently in VCounter"), 2, 1, true },
 61		{ tr("Enable VBlank IRQ generation"), 3 },
 62		{ tr("Enable HBlank IRQ generation"), 4 },
 63		{ tr("Enable VCounter IRQ generation"), 5 },
 64		{ tr("VCounter scanline"), 8, 8 },
 65	});
 66	// 0x04000006: VCOUNT
 67	s_registers.append({
 68		{ tr("Current scanline"), 0, 8, true },
 69	});
 70	// 0x04000008: BG0CNT
 71	s_registers.append({
 72		{ tr("Priority"), 0, 2 },
 73		{ tr("Tile data base (* 16kB)"), 2, 2 },
 74		{ tr("Enable mosaic"), 6 },
 75		{ tr("Enable 256-color"), 7 },
 76		{ tr("Tile map base (* 2kB)"), 8, 5 },
 77		{ tr("Background dimensions"), 14, 2 },
 78	});
 79	// 0x0400000A: BG1CNT
 80	s_registers.append({
 81		{ tr("Priority"), 0, 2 },
 82		{ tr("Tile data base (* 16kB)"), 2, 2 },
 83		{ tr("Enable mosaic"), 6 },
 84		{ tr("Enable 256-color"), 7 },
 85		{ tr("Tile map base (* 2kB)"), 8, 5 },
 86		{ tr("Background dimensions"), 14, 2 },
 87	});
 88	// 0x0400000C: BG2CNT
 89	s_registers.append({
 90		{ tr("Priority"), 0, 2 },
 91		{ tr("Tile data base (* 16kB)"), 2, 2 },
 92		{ tr("Enable mosaic"), 6 },
 93		{ tr("Enable 256-color"), 7 },
 94		{ tr("Tile map base (* 2kB)"), 8, 5 },
 95		{ tr("Overflow wraps"), 13 },
 96		{ tr("Background dimensions"), 14, 2 },
 97	});
 98	// 0x0400000E: BG3CNT
 99	s_registers.append({
100		{ tr("Priority"), 0, 2 },
101		{ tr("Tile data base (* 16kB)"), 2, 2 },
102		{ tr("Enable mosaic"), 6 },
103		{ tr("Enable 256-color"), 7 },
104		{ tr("Tile map base (* 2kB)"), 8, 5 },
105		{ tr("Overflow wraps"), 13 },
106		{ tr("Background dimensions"), 14, 2 },
107	});
108	// 0x04000010: BG0HOFS
109	s_registers.append({
110		{ tr("Horizontal offset"), 0, 9 },
111	});
112	// 0x04000012: BG0VOFS
113	s_registers.append({
114		{ tr("Vertical offset"), 0, 9 },
115	});
116	// 0x04000014: BG1HOFS
117	s_registers.append({
118		{ tr("Horizontal offset"), 0, 9 },
119	});
120	// 0x04000016: BG1VOFS
121	s_registers.append({
122		{ tr("Vertical offset"), 0, 9 },
123	});
124	// 0x04000018: BG2HOFS
125	s_registers.append({
126		{ tr("Horizontal offset"), 0, 9 },
127	});
128	// 0x0400001A: BG2VOFS
129	s_registers.append({
130		{ tr("Vertical offset"), 0, 9 },
131	});
132	// 0x0400001C: BG3HOFS
133	s_registers.append({
134		{ tr("Horizontal offset"), 0, 9 },
135	});
136	// 0x0400001E: BG3VOFS
137	s_registers.append({
138		{ tr("Vertical offset"), 0, 9 },
139	});
140	return s_registers;
141}
142
143IOViewer::IOViewer(GameController* controller, QWidget* parent)
144	: QDialog(parent)
145	, m_controller(controller)
146{
147	m_ui.setupUi(this);
148
149	for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
150		const char* reg = GBAIORegisterNames[i];
151		if (!reg) {
152			continue;
153		}
154		m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
155	}
156
157	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
158	m_ui.regValue->setFont(font);
159
160	connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
161	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
162	connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
163
164	m_b[0] = m_ui.b0;
165	m_b[1] = m_ui.b1;
166	m_b[2] = m_ui.b2;
167	m_b[3] = m_ui.b3;
168	m_b[4] = m_ui.b4;
169	m_b[5] = m_ui.b5;
170	m_b[6] = m_ui.b6;
171	m_b[7] = m_ui.b7;
172	m_b[8] = m_ui.b8;
173	m_b[9] = m_ui.b9;
174	m_b[10] = m_ui.bA;
175	m_b[11] = m_ui.bB;
176	m_b[12] = m_ui.bC;
177	m_b[13] = m_ui.bD;
178	m_b[14] = m_ui.bE;
179	m_b[15] = m_ui.bF;
180
181	for (int i = 0; i < 16; ++i) {
182		connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
183	}
184
185	selectRegister(0);
186}
187
188void IOViewer::updateRegister() {
189	m_value = 0;
190	uint16_t value = 0;
191	m_controller->threadInterrupt();
192	if (m_controller->isLoaded()) {
193		value = GBAIORead(m_controller->thread()->gba, m_register);
194	}
195	m_controller->threadContinue();
196
197	for (int i = 0; i < 16; ++i) {
198		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
199	}
200	m_value = value;
201}
202
203void IOViewer::bitFlipped() {
204	m_value = 0;
205	for (int i = 0; i < 16; ++i) {
206		m_value |= m_b[i]->isChecked() << i;
207	}
208	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
209}
210
211void IOViewer::writeback() {
212	m_controller->threadInterrupt();
213	if (m_controller->isLoaded()) {
214		GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
215	}
216	m_controller->threadContinue();
217	updateRegister();
218}
219
220void IOViewer::selectRegister(unsigned address) {
221	m_register = address;
222	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
223	if (box) {
224		// I can't believe there isn't a real way to do this...
225		while (!box->isEmpty()) {
226			QLayoutItem* item = box->takeAt(0);
227			if (item->widget()) {
228				delete item->widget();
229			}
230			delete item;
231		}
232	} else {
233		box = new QGridLayout;
234	}
235	if (registerDescriptions().count() > address >> 1) {
236		// TODO: Remove the check when done filling in register information
237		const RegisterDescription& description = registerDescriptions().at(address >> 1);
238		int i = 0;
239		for (const RegisterItem& ri : description) {
240			QLabel* label = new QLabel(ri.description);
241			box->addWidget(label, i, 0);
242			if (ri.size == 1) {
243				QCheckBox* check = new QCheckBox;
244				check->setEnabled(!ri.readonly);
245				box->addWidget(check, i, 1, Qt::AlignRight);
246				connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
247				connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
248			} else if (ri.items.empty()) {
249				QSpinBox* sbox = new QSpinBox;
250				sbox->setEnabled(!ri.readonly);
251				sbox->setMaximum((1 << ri.size) - 1);
252				box->addWidget(sbox, i, 1, Qt::AlignRight);
253				for (int b = 0; b < ri.size; ++b) {
254					connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, b, &ri](int v) {
255						m_b[ri.start + b]->setChecked(v & (1 << b));
256					});
257					auto connection = connect(m_b[ri.start + b], &QCheckBox::toggled, [sbox, this, b](bool t) {
258						int v = sbox->value() & ~(1 << b);
259						v |= t << b;
260						bool signalsBlocked = sbox->blockSignals(true);
261						sbox->setValue(v);
262						sbox->blockSignals(signalsBlocked);
263					});
264					connect(sbox, &QObject::destroyed, [connection, this, b, &ri]() {
265						bool signalsBlocked = m_b[ri.start + b]->blockSignals(true);
266						m_b[ri.start + b]->disconnect(connection);
267						m_b[ri.start + b]->blockSignals(signalsBlocked);
268					});
269				}
270			} else {
271				QButtonGroup* group = new QButtonGroup(box);
272				group->setExclusive(true);
273				for (int o = 0; o < 1 << ri.size; ++o) {
274					if (ri.items.at(o).isNull()) {
275						continue;
276					}
277					++i;
278					QRadioButton* button = new QRadioButton(ri.items.at(o));
279					button->setEnabled(!ri.readonly);
280					box->addWidget(button, i, 0, 1, 2, Qt::AlignLeft);
281					group->addButton(button, o);
282				}
283				for (int b = 0; b < ri.size; ++b) {
284					auto connection = connect(m_b[ri.start + b], &QCheckBox::toggled, [group, this, &ri](bool) {
285						unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
286						for (int i = 0; i < 1 << ri.size; ++i) {
287							QAbstractButton* button = group->button(i);
288							if (!button) {
289								continue;
290							}
291							bool signalsBlocked = button->blockSignals(true);
292							button->setChecked(i == v);
293							button->blockSignals(signalsBlocked);
294						}
295					});
296					connect(group, &QObject::destroyed, [connection, this, b, &ri]() {
297						m_b[ri.start + b]->disconnect(connection);
298					});
299				}
300				connect(group, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), [this, &ri](int v) {
301					for (int i = 0; i < ri.size; ++i) {
302						bool signalsBlocked = m_b[ri.start + i]->blockSignals(true);
303						m_b[ri.start + i]->setChecked(v & (1 << i));
304						m_b[ri.start + i]->blockSignals(signalsBlocked);
305					}
306				});
307			}
308			++i;
309		}
310	}
311	m_ui.regDescription->setLayout(box);
312	updateRegister();
313}
314
315void IOViewer::selectRegister() {
316	selectRegister(m_ui.regSelect->currentData().toUInt());
317}
318
319void IOViewer::buttonPressed(QAbstractButton* button) {
320	switch (m_ui.buttonBox->standardButton(button)) {
321	case QDialogButtonBox::Reset:
322		updateRegister();
323	 	break;
324	case QDialogButtonBox::Apply:
325	 	writeback();
326	 	break;
327	default:
328		break;
329	}
330}