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}