all repos — mgba @ d786077960771573a4782998308cf20d0b8da7b6

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	// 0x040000A8: Unused
541	s_registers.append(RegisterDescription());
542	// 0x040000AA: Unused
543	s_registers.append(RegisterDescription());
544	// 0x040000AC: Unused
545	s_registers.append(RegisterDescription());
546	// 0x040000AE: Unused
547	s_registers.append(RegisterDescription());
548	// 0x040000B0: DMA0SAD_LO
549	s_registers.append({
550		{ tr("Address (bottom)"), 0, 16 },
551	});
552	// 0x040000B2: DMA0SAD_HI
553	s_registers.append({
554		{ tr("Address (top)"), 0, 16 },
555	});
556	// 0x040000B4: DMA0DAD_LO
557	s_registers.append({
558		{ tr("Address (bottom)"), 0, 16 },
559	});
560	// 0x040000B6: DMA0DAD_HI
561	s_registers.append({
562		{ tr("Address (top)"), 0, 16 },
563	});
564	// 0x040000B8: DMA0CNT_LO
565	s_registers.append({
566		{ tr("Word count"), 0, 16 },
567	});
568	// 0x040000BA: DMA0CNT_HI
569	s_registers.append({
570		{ tr("Destination offset"), 5, 2, {
571			tr("Increment"),
572			tr("Decrement"),
573			tr("Fixed"),
574			tr("Increment and reload"),
575		} },
576		{ tr("Source offset"), 7, 2, {
577			tr("Increment"),
578			tr("Decrement"),
579			tr("Fixed"),
580			QString(),
581		} },
582		{ tr("Repeat"), 9 },
583		{ tr("32-bit"), 10 },
584		{ tr("Start timing"), 12, 2, {
585			tr("Immediate"),
586			tr("VBlank"),
587			tr("HBlank"),
588			QString(),
589		} },
590		{ tr("IRQ"), 14 },
591		{ tr("Enable"), 15 },
592	});
593	// 0x040000BC: DMA1SAD_LO
594	s_registers.append({
595		{ tr("Address (bottom)"), 0, 16 },
596	});
597	// 0x040000BE: DMA1SAD_HI
598	s_registers.append({
599		{ tr("Address (top)"), 0, 16 },
600	});
601	// 0x040000C0: DMA1DAD_LO
602	s_registers.append({
603		{ tr("Address (bottom)"), 0, 16 },
604	});
605	// 0x040000C2: DMA1DAD_HI
606	s_registers.append({
607		{ tr("Address (top)"), 0, 16 },
608	});
609	// 0x040000C4: DMA1CNT_LO
610	s_registers.append({
611		{ tr("Word count"), 0, 16 },
612	});
613	// 0x040000C6: DMA1CNT_HI
614	s_registers.append({
615		{ tr("Destination offset"), 5, 2, {
616			tr("Increment"),
617			tr("Decrement"),
618			tr("Fixed"),
619			tr("Increment and reload"),
620		} },
621		{ tr("Source offset"), 7, 2, {
622			tr("Increment"),
623			tr("Decrement"),
624			tr("Fixed"),
625			QString(),
626		} },
627		{ tr("Repeat"), 9 },
628		{ tr("32-bit"), 10 },
629		{ tr("Start timing"), 12, 2, {
630			tr("Immediate"),
631			tr("VBlank"),
632			tr("HBlank"),
633			tr("Audio FIFO"),
634		} },
635		{ tr("IRQ"), 14 },
636		{ tr("Enable"), 15 },
637	});
638	// 0x040000C8: DMA2SAD_LO
639	s_registers.append({
640		{ tr("Address (bottom)"), 0, 16 },
641	});
642	// 0x040000CA: DMA2SAD_HI
643	s_registers.append({
644		{ tr("Address (top)"), 0, 16 },
645	});
646	// 0x040000CC: DMA2DAD_LO
647	s_registers.append({
648		{ tr("Address (bottom)"), 0, 16 },
649	});
650	// 0x040000CE: DMA2DAD_HI
651	s_registers.append({
652		{ tr("Address (top)"), 0, 16 },
653	});
654	// 0x040000D0: DMA2CNT_LO
655	s_registers.append({
656		{ tr("Word count"), 0, 16 },
657	});
658	// 0x040000D2: DMA2CNT_HI
659	s_registers.append({
660		{ tr("Destination offset"), 5, 2, {
661			tr("Increment"),
662			tr("Decrement"),
663			tr("Fixed"),
664			tr("Increment and reload"),
665		} },
666		{ tr("Source offset"), 7, 2, {
667			tr("Increment"),
668			tr("Decrement"),
669			tr("Fixed"),
670			QString(),
671		} },
672		{ tr("Repeat"), 9 },
673		{ tr("32-bit"), 10 },
674		{ tr("Start timing"), 12, 2, {
675			tr("Immediate"),
676			tr("VBlank"),
677			tr("HBlank"),
678			tr("Audio FIFO"),
679		} },
680		{ tr("IRQ"), 14 },
681		{ tr("Enable"), 15 },
682	});
683	// 0x040000D4: DMA3SAD_LO
684	s_registers.append({
685		{ tr("Address (bottom)"), 0, 16 },
686	});
687	// 0x040000D6: DMA3SAD_HI
688	s_registers.append({
689		{ tr("Address (top)"), 0, 16 },
690	});
691	// 0x040000D8: DMA3DAD_LO
692	s_registers.append({
693		{ tr("Address (bottom)"), 0, 16 },
694	});
695	// 0x040000DA: DMA3DAD_HI
696	s_registers.append({
697		{ tr("Address (top)"), 0, 16 },
698	});
699	// 0x040000DC: DMA3CNT_LO
700	s_registers.append({
701		{ tr("Word count"), 0, 16 },
702	});
703	// 0x040000DE: DMA3CNT_HI
704	s_registers.append({
705		{ tr("Destination offset"), 5, 2, {
706			tr("Increment"),
707			tr("Decrement"),
708			tr("Fixed"),
709			tr("Increment and reload"),
710		} },
711		{ tr("Source offset"), 7, 2, {
712			tr("Increment"),
713			tr("Decrement"),
714			tr("Fixed"),
715			tr("Video Capture"),
716		} },
717		{ tr("DRQ"), 8 },
718		{ tr("Repeat"), 9 },
719		{ tr("32-bit"), 10 },
720		{ tr("Start timing"), 12, 2, {
721			tr("Immediate"),
722			tr("VBlank"),
723			tr("HBlank"),
724			tr("Audio FIFO"),
725		} },
726		{ tr("IRQ"), 14 },
727		{ tr("Enable"), 15 },
728	});
729	return s_registers;
730}
731
732IOViewer::IOViewer(GameController* controller, QWidget* parent)
733	: QDialog(parent)
734	, m_controller(controller)
735{
736	m_ui.setupUi(this);
737
738	for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
739		const char* reg = GBAIORegisterNames[i];
740		if (!reg) {
741			continue;
742		}
743		m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
744	}
745
746	const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
747	m_ui.regValue->setFont(font);
748
749	connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
750	connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
751	connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
752
753	m_b[0] = m_ui.b0;
754	m_b[1] = m_ui.b1;
755	m_b[2] = m_ui.b2;
756	m_b[3] = m_ui.b3;
757	m_b[4] = m_ui.b4;
758	m_b[5] = m_ui.b5;
759	m_b[6] = m_ui.b6;
760	m_b[7] = m_ui.b7;
761	m_b[8] = m_ui.b8;
762	m_b[9] = m_ui.b9;
763	m_b[10] = m_ui.bA;
764	m_b[11] = m_ui.bB;
765	m_b[12] = m_ui.bC;
766	m_b[13] = m_ui.bD;
767	m_b[14] = m_ui.bE;
768	m_b[15] = m_ui.bF;
769
770	for (int i = 0; i < 16; ++i) {
771		connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
772	}
773
774	selectRegister(0);
775}
776
777void IOViewer::updateRegister() {
778	m_value = 0;
779	uint16_t value = 0;
780	m_controller->threadInterrupt();
781	if (m_controller->isLoaded()) {
782		value = GBAIORead(m_controller->thread()->gba, m_register);
783	}
784	m_controller->threadContinue();
785
786	for (int i = 0; i < 16; ++i) {
787		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
788	}
789	m_value = value;
790	emit valueChanged();
791}
792
793void IOViewer::bitFlipped() {
794	m_value = 0;
795	for (int i = 0; i < 16; ++i) {
796		m_value |= m_b[i]->isChecked() << i;
797	}
798	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
799	emit valueChanged();
800}
801
802void IOViewer::writeback() {
803	m_controller->threadInterrupt();
804	if (m_controller->isLoaded()) {
805		GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
806	}
807	m_controller->threadContinue();
808	updateRegister();
809}
810
811void IOViewer::selectRegister(unsigned address) {
812	m_register = address;
813	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
814	if (box) {
815		// I can't believe there isn't a real way to do this...
816		while (!box->isEmpty()) {
817			QLayoutItem* item = box->takeAt(0);
818			if (item->widget()) {
819				delete item->widget();
820			}
821			delete item;
822		}
823	} else {
824		box = new QGridLayout;
825	}
826	if (registerDescriptions().count() > address >> 1) {
827		// TODO: Remove the check when done filling in register information
828		const RegisterDescription& description = registerDescriptions().at(address >> 1);
829		int i = 0;
830		for (const RegisterItem& ri : description) {
831			QLabel* label = new QLabel(ri.description);
832			box->addWidget(label, i, 0);
833			if (ri.size == 1) {
834				QCheckBox* check = new QCheckBox;
835				check->setEnabled(!ri.readonly);
836				box->addWidget(check, i, 1, Qt::AlignRight);
837				connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
838				connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
839			} else if (ri.items.empty()) {
840				QSpinBox* sbox = new QSpinBox;
841				sbox->setEnabled(!ri.readonly);
842				sbox->setMaximum((1 << ri.size) - 1);
843				box->addWidget(sbox, i, 1, Qt::AlignRight);
844				auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
845					int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
846					bool signalsBlocked = sbox->blockSignals(true);
847					sbox->setValue(v);
848					sbox->blockSignals(signalsBlocked);
849				});
850				connect(sbox, &QObject::destroyed, [connection, this]() {
851					this->disconnect(connection);
852				});
853			} else {
854				QButtonGroup* group = new QButtonGroup(box);
855				group->setExclusive(true);
856				for (int o = 0; o < 1 << ri.size; ++o) {
857					if (ri.items.at(o).isNull()) {
858						continue;
859					}
860					++i;
861					QRadioButton* button = new QRadioButton(ri.items.at(o));
862					button->setEnabled(!ri.readonly);
863					box->addWidget(button, i, 0, 1, 2, Qt::AlignLeft);
864					group->addButton(button, o);
865				}
866
867				auto connection = connect(this, &IOViewer::valueChanged, [group, this, &ri]() {
868					unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
869					for (int i = 0; i < 1 << ri.size; ++i) {
870						QAbstractButton* button = group->button(i);
871						if (!button) {
872							continue;
873						}
874						bool signalsBlocked = button->blockSignals(true);
875						button->setChecked(i == v);
876						button->blockSignals(signalsBlocked);
877					}
878				});
879				connect(group, &QObject::destroyed, [connection, this]() {
880					this->disconnect(connection);
881				});
882
883			}
884			++i;
885		}
886	}
887	m_ui.regDescription->setLayout(box);
888	updateRegister();
889}
890
891void IOViewer::selectRegister() {
892	selectRegister(m_ui.regSelect->currentData().toUInt());
893}
894
895void IOViewer::buttonPressed(QAbstractButton* button) {
896	switch (m_ui.buttonBox->standardButton(button)) {
897	case QDialogButtonBox::Reset:
898		updateRegister();
899	 	break;
900	case QDialogButtonBox::Apply:
901	 	writeback();
902	 	break;
903	default:
904		break;
905	}
906}