all repos — mgba @ 066ba16864236e943fa1eedf61252b5595c5de42

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	// 0x04000056: Unused
306	s_registers.append(RegisterDescription());
307	// 0x04000058: Unused
308	s_registers.append(RegisterDescription());
309	// 0x0400005A: Unused
310	s_registers.append(RegisterDescription());
311	// 0x0400005C: Unused
312	s_registers.append(RegisterDescription());
313	// 0x0400005E: Unused
314	s_registers.append(RegisterDescription());
315	// 0x04000060: SOUND1CNT_LO
316	s_registers.append({
317		{ tr("Sweep shifts"), 0, 3 },
318		{ tr("Sweep subtract"), 3 },
319		{ tr("Sweep time (in 1/128s)"), 4, 3 },
320	});
321	// 0x04000062: SOUND1CNT_HI
322	s_registers.append({
323		{ tr("Sound length"), 0, 6 },
324		{ tr("Duty cycle"),  6, 2 },
325		{ tr("Envelope step time"), 8, 3 },
326		{ tr("Envelope increase"), 11 },
327		{ tr("Initial volume"), 12, 4 },
328	});
329	// 0x04000064: SOUND1CNT_X
330	s_registers.append({
331		{ tr("Sound frequency"), 0, 11 },
332		{ tr("Timed"),  14 },
333		{ tr("Reset"), 15 },
334	});
335	// 0x04000066: Unused
336	s_registers.append(RegisterDescription());
337	// 0x04000068: SOUND2CNT_LO
338	s_registers.append({
339		{ tr("Sound length"), 0, 6 },
340		{ tr("Duty cycle"),  6, 2 },
341		{ tr("Envelope step time"), 8, 3 },
342		{ tr("Envelope increase"), 11 },
343		{ tr("Initial volume"), 12, 4 },
344	});
345	// 0x0400006A: Unused
346	s_registers.append(RegisterDescription());
347	// 0x0400006C: SOUND2CNT_HI
348	s_registers.append({
349		{ tr("Sound frequency"), 0, 11 },
350		{ tr("Timed"),  14 },
351		{ tr("Reset"), 15 },
352	});
353	// 0x0400006E: Unused
354	s_registers.append(RegisterDescription());
355	// 0x04000070: SOUND3CNT_LO
356	s_registers.append({
357		{ tr("Double-size wave table"), 5 },
358		{ tr("Active wave table"),  6 },
359		{ tr("Enable channel 3"), 7 },
360	});
361	// 0x04000072: SOUND3CNT_HI
362	s_registers.append({
363		{ tr("Sound length"), 0, 8 },
364		{ tr("Volume"),  13, 3, {
365			tr("0%"),
366			tr("100%"),
367			tr("50%"),
368			tr("25%"),
369			tr("75%"),
370			tr("75%"),
371			tr("75%"),
372			tr("75%")
373		} },
374	});
375	// 0x04000074: SOUND3CNT_X
376	s_registers.append({
377		{ tr("Sound frequency"), 0, 11 },
378		{ tr("Timed"),  14 },
379		{ tr("Reset"), 15 },
380	});
381	// 0x04000076: Unused
382	s_registers.append(RegisterDescription());
383	// 0x04000078: SOUND4CNT_LO
384	s_registers.append({
385		{ tr("Sound length"), 0, 6 },
386		{ tr("Envelope step time"), 8, 3 },
387		{ tr("Envelope increase"), 11 },
388		{ tr("Initial volume"), 12, 4 },
389	});
390	// 0x0400007A: Unused
391	s_registers.append(RegisterDescription());
392	// 0x0400007C: SOUND4CNT_HI
393	s_registers.append({
394		{ tr("Clock divider"), 0, 3 },
395		{ tr("Register stages"), 3, 1, {
396			tr("15"),
397			tr("7"),
398		} },
399		{ tr("Shifter frequency"), 4, 4 },
400		{ tr("Timed"),  14 },
401		{ tr("Reset"), 15 },
402	});
403	// 0x0400007E: Unused
404	s_registers.append(RegisterDescription());
405	// 0x04000080: SOUNDCNT_LO
406	s_registers.append({
407		{ tr("PSG volume right"), 0, 3 },
408		{ tr("PSG volume left"), 4, 3 },
409		{ tr("Enable channel 1 right"), 8 },
410		{ tr("Enable channel 2 right"), 9 },
411		{ tr("Enable channel 3 right"), 10 },
412		{ tr("Enable channel 4 right"), 11 },
413		{ tr("Enable channel 1 left"), 12 },
414		{ tr("Enable channel 2 left"), 13 },
415		{ tr("Enable channel 3 left"), 14 },
416		{ tr("Enable channel 4 left"), 15 },
417	});
418	// 0x04000082: SOUNDCNT_HI
419	s_registers.append({
420		{ tr("PSG master volume"), 0, 2, {
421			tr("25%"),
422			tr("50%"),
423			tr("100%"),
424			QString()
425		} },
426		{ tr("Loud channel A"), 2 },
427		{ tr("Loud channel B"), 3 },
428		{ tr("Enable channel A right"), 8 },
429		{ tr("Enable channel A left"), 9 },
430		{ tr("Channel A timer"), 10, 1, {
431			tr("0"),
432			tr("1"),
433		} },
434		{ tr("Channel A reset"), 11 },
435		{ tr("Enable channel B right"), 12 },
436		{ tr("Enable channel B left"), 13 },
437		{ tr("Channel B timer"), 14, 1, {
438			tr("0"),
439			tr("1"),
440		} },
441		{ tr("Channel B reset"), 15 },
442	});
443	// 0x04000084: SOUNDCNT_LO
444	s_registers.append({
445		{ tr("Active channel 1"), 0, 1, true },
446		{ tr("Active channel 2"), 1, 1, true },
447		{ tr("Active channel 3"), 2, 1, true },
448		{ tr("Active channel 4"), 3, 1, true },
449		{ tr("Enable audio"), 7 },
450	});
451	// 0x04000086: Unused
452	s_registers.append(RegisterDescription());
453	// 0x04000088: SOUNDBIAS
454	s_registers.append({
455		{ tr("Bias"), 0, 10 },
456		{ tr("Resolution"), 14, 2 },
457	});
458	// 0x0400008A: Unused
459	s_registers.append(RegisterDescription());
460	// 0x0400008C: Unused
461	s_registers.append(RegisterDescription());
462	// 0x0400008E: Unused
463	s_registers.append(RegisterDescription());
464	// 0x04000090: WAVE_RAM0_LO
465	s_registers.append({
466		{ tr("Sample"), 0, 4 },
467		{ tr("Sample"), 4, 4 },
468		{ tr("Sample"), 8, 4 },
469		{ tr("Sample"), 12, 4 },
470	});
471	// 0x04000092: WAVE_RAM0_HI
472	s_registers.append({
473		{ tr("Sample"), 0, 4 },
474		{ tr("Sample"), 4, 4 },
475		{ tr("Sample"), 8, 4 },
476		{ tr("Sample"), 12, 4 },
477	});
478	// 0x04000094: WAVE_RAM1_LO
479	s_registers.append({
480		{ tr("Sample"), 0, 4 },
481		{ tr("Sample"), 4, 4 },
482		{ tr("Sample"), 8, 4 },
483		{ tr("Sample"), 12, 4 },
484	});
485	// 0x04000096: WAVE_RAM1_HI
486	s_registers.append({
487		{ tr("Sample"), 0, 4 },
488		{ tr("Sample"), 4, 4 },
489		{ tr("Sample"), 8, 4 },
490		{ tr("Sample"), 12, 4 },
491	});
492	// 0x04000098: WAVE_RAM2_LO
493	s_registers.append({
494		{ tr("Sample"), 0, 4 },
495		{ tr("Sample"), 4, 4 },
496		{ tr("Sample"), 8, 4 },
497		{ tr("Sample"), 12, 4 },
498	});
499	// 0x0400009A: WAVE_RAM2_HI
500	s_registers.append({
501		{ tr("Sample"), 0, 4 },
502		{ tr("Sample"), 4, 4 },
503		{ tr("Sample"), 8, 4 },
504		{ tr("Sample"), 12, 4 },
505	});
506	// 0x0400009C: WAVE_RAM3_LO
507	s_registers.append({
508		{ tr("Sample"), 0, 4 },
509		{ tr("Sample"), 4, 4 },
510		{ tr("Sample"), 8, 4 },
511		{ tr("Sample"), 12, 4 },
512	});
513	// 0x0400009E: WAVE_RAM0_HI
514	s_registers.append({
515		{ tr("Sample"), 0, 4 },
516		{ tr("Sample"), 4, 4 },
517		{ tr("Sample"), 8, 4 },
518		{ tr("Sample"), 12, 4 },
519	});
520	// 0x040000A0: FIFO_A_LO
521	s_registers.append({
522		{ tr("Sample"), 0, 8 },
523		{ tr("Sample"), 8, 8 },
524	});
525	// 0x040000A2: FIFO_A_HI
526	s_registers.append({
527		{ tr("Sample"), 0, 8 },
528		{ tr("Sample"), 8, 8 },
529	});
530	// 0x040000A4: FIFO_B_LO
531	s_registers.append({
532		{ tr("Sample"), 0, 8 },
533		{ tr("Sample"), 8, 8 },
534	});
535	// 0x040000A6: FIFO_B_HI
536	s_registers.append({
537		{ tr("Sample"), 0, 8 },
538		{ tr("Sample"), 8, 8 },
539	});
540	// 0x040000AA: Unused
541	s_registers.append(RegisterDescription());
542	// 0x040000AC: Unused
543	s_registers.append(RegisterDescription());
544	// 0x040000AE: Unused
545	s_registers.append(RegisterDescription());
546	return s_registers;
547}
548
549IOViewer::IOViewer(GameController* controller, QWidget* parent)
550	: QDialog(parent)
551	, m_controller(controller)
552{
553	m_ui.setupUi(this);
554
555	for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
556		const char* reg = GBAIORegisterNames[i];
557		if (!reg) {
558			continue;
559		}
560		m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
561	}
562
563	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
564	m_ui.regValue->setFont(font);
565
566	connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
567	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
568	connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
569
570	m_b[0] = m_ui.b0;
571	m_b[1] = m_ui.b1;
572	m_b[2] = m_ui.b2;
573	m_b[3] = m_ui.b3;
574	m_b[4] = m_ui.b4;
575	m_b[5] = m_ui.b5;
576	m_b[6] = m_ui.b6;
577	m_b[7] = m_ui.b7;
578	m_b[8] = m_ui.b8;
579	m_b[9] = m_ui.b9;
580	m_b[10] = m_ui.bA;
581	m_b[11] = m_ui.bB;
582	m_b[12] = m_ui.bC;
583	m_b[13] = m_ui.bD;
584	m_b[14] = m_ui.bE;
585	m_b[15] = m_ui.bF;
586
587	for (int i = 0; i < 16; ++i) {
588		connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
589	}
590
591	selectRegister(0);
592}
593
594void IOViewer::updateRegister() {
595	m_value = 0;
596	uint16_t value = 0;
597	m_controller->threadInterrupt();
598	if (m_controller->isLoaded()) {
599		value = GBAIORead(m_controller->thread()->gba, m_register);
600	}
601	m_controller->threadContinue();
602
603	for (int i = 0; i < 16; ++i) {
604		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
605	}
606	m_value = value;
607	emit valueChanged();
608}
609
610void IOViewer::bitFlipped() {
611	m_value = 0;
612	for (int i = 0; i < 16; ++i) {
613		m_value |= m_b[i]->isChecked() << i;
614	}
615	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
616	emit valueChanged();
617}
618
619void IOViewer::writeback() {
620	m_controller->threadInterrupt();
621	if (m_controller->isLoaded()) {
622		GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
623	}
624	m_controller->threadContinue();
625	updateRegister();
626}
627
628void IOViewer::selectRegister(unsigned address) {
629	m_register = address;
630	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
631	if (box) {
632		// I can't believe there isn't a real way to do this...
633		while (!box->isEmpty()) {
634			QLayoutItem* item = box->takeAt(0);
635			if (item->widget()) {
636				delete item->widget();
637			}
638			delete item;
639		}
640	} else {
641		box = new QGridLayout;
642	}
643	if (registerDescriptions().count() > address >> 1) {
644		// TODO: Remove the check when done filling in register information
645		const RegisterDescription& description = registerDescriptions().at(address >> 1);
646		int i = 0;
647		for (const RegisterItem& ri : description) {
648			QLabel* label = new QLabel(ri.description);
649			box->addWidget(label, i, 0);
650			if (ri.size == 1) {
651				QCheckBox* check = new QCheckBox;
652				check->setEnabled(!ri.readonly);
653				box->addWidget(check, i, 1, Qt::AlignRight);
654				connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
655				connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
656			} else if (ri.items.empty()) {
657				QSpinBox* sbox = new QSpinBox;
658				sbox->setEnabled(!ri.readonly);
659				sbox->setMaximum((1 << ri.size) - 1);
660				box->addWidget(sbox, i, 1, Qt::AlignRight);
661				auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
662					int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
663					bool signalsBlocked = sbox->blockSignals(true);
664					sbox->setValue(v);
665					sbox->blockSignals(signalsBlocked);
666				});
667				connect(sbox, &QObject::destroyed, [connection, this]() {
668					this->disconnect(connection);
669				});
670			} else {
671				QButtonGroup* group = new QButtonGroup(box);
672				group->setExclusive(true);
673				for (int o = 0; o < 1 << ri.size; ++o) {
674					if (ri.items.at(o).isNull()) {
675						continue;
676					}
677					++i;
678					QRadioButton* button = new QRadioButton(ri.items.at(o));
679					button->setEnabled(!ri.readonly);
680					box->addWidget(button, i, 0, 1, 2, Qt::AlignLeft);
681					group->addButton(button, o);
682				}
683
684				auto connection = connect(this, &IOViewer::valueChanged, [group, this, &ri]() {
685					unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
686					for (int i = 0; i < 1 << ri.size; ++i) {
687						QAbstractButton* button = group->button(i);
688						if (!button) {
689							continue;
690						}
691						bool signalsBlocked = button->blockSignals(true);
692						button->setChecked(i == v);
693						button->blockSignals(signalsBlocked);
694					}
695				});
696				connect(group, &QObject::destroyed, [connection, this]() {
697					this->disconnect(connection);
698				});
699
700			}
701			++i;
702		}
703	}
704	m_ui.regDescription->setLayout(box);
705	updateRegister();
706}
707
708void IOViewer::selectRegister() {
709	selectRegister(m_ui.regSelect->currentData().toUInt());
710}
711
712void IOViewer::buttonPressed(QAbstractButton* button) {
713	switch (m_ui.buttonBox->standardButton(button)) {
714	case QDialogButtonBox::Reset:
715		updateRegister();
716	 	break;
717	case QDialogButtonBox::Apply:
718	 	writeback();
719	 	break;
720	default:
721		break;
722	}
723}