all repos — mgba @ d357b9328505447b15cd442a89b5e8f54953e455

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	// 0x04000020: BG2PA
141	s_registers.append({
142		{ tr("Fractional part"), 0, 8 },
143		{ tr("Integer part"), 8, 8 },
144	});
145	// 0x04000022: BG2PB
146	s_registers.append({
147		{ tr("Fractional part"), 0, 8 },
148		{ tr("Integer part"), 8, 8 },
149	});
150	// 0x04000024: BG2PC
151	s_registers.append({
152		{ tr("Fractional part"), 0, 8 },
153		{ tr("Integer part"), 8, 8 },
154	});
155	// 0x04000026: BG2PD
156	s_registers.append({
157		{ tr("Fractional part"), 0, 8 },
158		{ tr("Integer part"), 8, 8 },
159	});
160	// 0x04000028: BG2X_LO
161	s_registers.append({
162		{ tr("Fractional part"), 0, 8 },
163		{ tr("Integer part (bottom)"), 8, 8 },
164	});
165	// 0x0400002A: BG2X_HI
166	s_registers.append({
167		{ tr("Integer part (top)"), 0, 12 },
168	});
169	// 0x0400002C: BG2Y_LO
170	s_registers.append({
171		{ tr("Fractional part"), 0, 8 },
172		{ tr("Integer part (bottom)"), 8, 8 },
173	});
174	// 0x0400002E: BG2Y_HI
175	s_registers.append({
176		{ tr("Integer part (top)"), 0, 12 },
177	});
178	// 0x04000030: BG3PA
179	s_registers.append({
180		{ tr("Fractional part"), 0, 8 },
181		{ tr("Integer part"), 8, 8 },
182	});
183	// 0x04000032: BG3PB
184	s_registers.append({
185		{ tr("Fractional part"), 0, 8 },
186		{ tr("Integer part"), 8, 8 },
187	});
188	// 0x04000034: BG3PC
189	s_registers.append({
190		{ tr("Fractional part"), 0, 8 },
191		{ tr("Integer part"), 8, 8 },
192	});
193	// 0x04000036: BG3PD
194	s_registers.append({
195		{ tr("Fractional part"), 0, 8 },
196		{ tr("Integer part"), 8, 8 },
197	});
198	// 0x04000038: BG3X_LO
199	s_registers.append({
200		{ tr("Fractional part"), 0, 8 },
201		{ tr("Integer part (bottom)"), 8, 8 },
202	});
203	// 0x0400003A: BG3X_HI
204	s_registers.append({
205		{ tr("Integer part (top)"), 0, 12 },
206	});
207	// 0x0400003C: BG3Y_LO
208	s_registers.append({
209		{ tr("Fractional part"), 0, 8 },
210		{ tr("Integer part (bottom)"), 8, 8 },
211	});
212	// 0x0400003E: BG3Y_HI
213	s_registers.append({
214		{ tr("Integer part (top)"), 0, 12 },
215	});
216	// 0x04000040: WIN0H
217	s_registers.append({
218		{ tr("End x"), 0, 8 },
219		{ tr("Start x"), 8, 8 },
220	});
221	// 0x04000042: WIN1H
222	s_registers.append({
223		{ tr("End x"), 0, 8 },
224		{ tr("Start x"), 8, 8 },
225	});
226	// 0x04000044: WIN0V
227	s_registers.append({
228		{ tr("End y"), 0, 8 },
229		{ tr("Start y"), 8, 8 },
230	});
231	// 0x04000046: WIN1V
232	s_registers.append({
233		{ tr("End y"), 0, 8 },
234		{ tr("Start y"), 8, 8 },
235	});
236	// 0x04000048: WININ
237	s_registers.append({
238		{ tr("Window 0 enable BG 0"), 0 },
239		{ tr("Window 0 enable BG 1"), 1 },
240		{ tr("Window 0 enable BG 2"), 2 },
241		{ tr("Window 0 enable BG 3"), 3 },
242		{ tr("Window 0 enable OBJ"), 4 },
243		{ tr("Window 0 enable blend"), 5 },
244		{ tr("Window 1 enable BG 0"), 8 },
245		{ tr("Window 1 enable BG 1"), 9 },
246		{ tr("Window 1 enable BG 2"), 10 },
247		{ tr("Window 1 enable BG 3"), 11 },
248		{ tr("Window 1 enable OBJ"), 12 },
249		{ tr("Window 1 enable blend"), 13 },
250	});
251	// 0x0400004A: WINOUT
252	s_registers.append({
253		{ tr("Outside window enable BG 0"), 0 },
254		{ tr("Outside window enable BG 1"), 1 },
255		{ tr("Outside window enable BG 2"), 2 },
256		{ tr("Outside window enable BG 3"), 3 },
257		{ tr("Outside window enable OBJ"), 4 },
258		{ tr("Outside window enable blend"), 5 },
259		{ tr("OBJ window enable BG 0"), 8 },
260		{ tr("OBJ window enable BG 1"), 9 },
261		{ tr("OBJ window enable BG 2"), 10 },
262		{ tr("OBJ window enable BG 3"), 11 },
263		{ tr("OBJ window enable OBJ"), 12 },
264		{ tr("OBJ window enable blend"), 13 },
265	});
266	// 0x0400004C: MOSAIC
267	s_registers.append({
268		{ tr("Background mosaic size vertical"), 0, 4 },
269		{ tr("Background mosaic size horizontal"), 4, 4 },
270		{ tr("Object mosaic size vertical"), 8, 4 },
271		{ tr("Object mosaic size horizontal"), 12, 4 },
272	});
273	// 0x0400004E: Unused
274	s_registers.append(RegisterDescription());
275	// 0x04000050: BLDCNT
276	s_registers.append({
277		{ tr("BG 0 target 1"), 0 },
278		{ tr("BG 1 target 1"), 1 },
279		{ tr("BG 2 target 1"), 2 },
280		{ tr("BG 3 target 1"), 3 },
281		{ tr("OBJ target 1"), 4 },
282		{ tr("Backdrop target 1"), 5 },
283		{ tr("Blend mode"), 6, 2, {
284			tr("Disabled"),
285			tr("Additive blending"),
286			tr("Brighten"),
287			tr("Darken"),
288		} },
289		{ tr("BG 0 target 2"), 8 },
290		{ tr("BG 1 target 2"), 9 },
291		{ tr("BG 2 target 2"), 10 },
292		{ tr("BG 3 target 2"), 11 },
293		{ tr("OBJ target 2"), 12 },
294		{ tr("Backdrop target 2"), 13 },
295	});
296	// 0x04000052: BLDALPHA
297	s_registers.append({
298		{ tr("Blend A (target 1)"), 0, 5 },
299		{ tr("Blend B (target 2)"), 8, 5 },
300	});
301	// 0x04000054: BLDY
302	s_registers.append({
303		{ tr("Blend Y"), 0, 5 },
304	});
305	return s_registers;
306}
307
308IOViewer::IOViewer(GameController* controller, QWidget* parent)
309	: QDialog(parent)
310	, m_controller(controller)
311{
312	m_ui.setupUi(this);
313
314	for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
315		const char* reg = GBAIORegisterNames[i];
316		if (!reg) {
317			continue;
318		}
319		m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
320	}
321
322	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
323	m_ui.regValue->setFont(font);
324
325	connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
326	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
327	connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
328
329	m_b[0] = m_ui.b0;
330	m_b[1] = m_ui.b1;
331	m_b[2] = m_ui.b2;
332	m_b[3] = m_ui.b3;
333	m_b[4] = m_ui.b4;
334	m_b[5] = m_ui.b5;
335	m_b[6] = m_ui.b6;
336	m_b[7] = m_ui.b7;
337	m_b[8] = m_ui.b8;
338	m_b[9] = m_ui.b9;
339	m_b[10] = m_ui.bA;
340	m_b[11] = m_ui.bB;
341	m_b[12] = m_ui.bC;
342	m_b[13] = m_ui.bD;
343	m_b[14] = m_ui.bE;
344	m_b[15] = m_ui.bF;
345
346	for (int i = 0; i < 16; ++i) {
347		connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
348	}
349
350	selectRegister(0);
351}
352
353void IOViewer::updateRegister() {
354	m_value = 0;
355	uint16_t value = 0;
356	m_controller->threadInterrupt();
357	if (m_controller->isLoaded()) {
358		value = GBAIORead(m_controller->thread()->gba, m_register);
359	}
360	m_controller->threadContinue();
361
362	for (int i = 0; i < 16; ++i) {
363		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
364	}
365	m_value = value;
366	emit valueChanged();
367}
368
369void IOViewer::bitFlipped() {
370	m_value = 0;
371	for (int i = 0; i < 16; ++i) {
372		m_value |= m_b[i]->isChecked() << i;
373	}
374	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
375	emit valueChanged();
376}
377
378void IOViewer::writeback() {
379	m_controller->threadInterrupt();
380	if (m_controller->isLoaded()) {
381		GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
382	}
383	m_controller->threadContinue();
384	updateRegister();
385}
386
387void IOViewer::selectRegister(unsigned address) {
388	m_register = address;
389	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
390	if (box) {
391		// I can't believe there isn't a real way to do this...
392		while (!box->isEmpty()) {
393			QLayoutItem* item = box->takeAt(0);
394			if (item->widget()) {
395				delete item->widget();
396			}
397			delete item;
398		}
399	} else {
400		box = new QGridLayout;
401	}
402	if (registerDescriptions().count() > address >> 1) {
403		// TODO: Remove the check when done filling in register information
404		const RegisterDescription& description = registerDescriptions().at(address >> 1);
405		int i = 0;
406		for (const RegisterItem& ri : description) {
407			QLabel* label = new QLabel(ri.description);
408			box->addWidget(label, i, 0);
409			if (ri.size == 1) {
410				QCheckBox* check = new QCheckBox;
411				check->setEnabled(!ri.readonly);
412				box->addWidget(check, i, 1, Qt::AlignRight);
413				connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
414				connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
415			} else if (ri.items.empty()) {
416				QSpinBox* sbox = new QSpinBox;
417				sbox->setEnabled(!ri.readonly);
418				sbox->setMaximum((1 << ri.size) - 1);
419				box->addWidget(sbox, i, 1, Qt::AlignRight);
420				auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
421					int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
422					bool signalsBlocked = sbox->blockSignals(true);
423					sbox->setValue(v);
424					sbox->blockSignals(signalsBlocked);
425				});
426				connect(sbox, &QObject::destroyed, [connection, this]() {
427					this->disconnect(connection);
428				});
429			} else {
430				QButtonGroup* group = new QButtonGroup(box);
431				group->setExclusive(true);
432				for (int o = 0; o < 1 << ri.size; ++o) {
433					if (ri.items.at(o).isNull()) {
434						continue;
435					}
436					++i;
437					QRadioButton* button = new QRadioButton(ri.items.at(o));
438					button->setEnabled(!ri.readonly);
439					box->addWidget(button, i, 0, 1, 2, Qt::AlignLeft);
440					group->addButton(button, o);
441				}
442
443				auto connection = connect(this, &IOViewer::valueChanged, [group, this, &ri]() {
444					unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
445					for (int i = 0; i < 1 << ri.size; ++i) {
446						QAbstractButton* button = group->button(i);
447						if (!button) {
448							continue;
449						}
450						bool signalsBlocked = button->blockSignals(true);
451						button->setChecked(i == v);
452						button->blockSignals(signalsBlocked);
453					}
454				});
455				connect(group, &QObject::destroyed, [connection, this]() {
456					this->disconnect(connection);
457				});
458
459			}
460			++i;
461		}
462	}
463	m_ui.regDescription->setLayout(box);
464	updateRegister();
465}
466
467void IOViewer::selectRegister() {
468	selectRegister(m_ui.regSelect->currentData().toUInt());
469}
470
471void IOViewer::buttonPressed(QAbstractButton* button) {
472	switch (m_ui.buttonBox->standardButton(button)) {
473	case QDialogButtonBox::Reset:
474		updateRegister();
475	 	break;
476	case QDialogButtonBox::Apply:
477	 	writeback();
478	 	break;
479	default:
480		break;
481	}
482}