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 <QComboBox>
11#include <QFontDatabase>
12#include <QGridLayout>
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 // 0x040000E0: Unused
730 s_registers.append(RegisterDescription());
731 // 0x040000E2: Unused
732 s_registers.append(RegisterDescription());
733 // 0x040000E4: Unused
734 s_registers.append(RegisterDescription());
735 // 0x040000E6: Unused
736 s_registers.append(RegisterDescription());
737 // 0x040000E8: Unused
738 s_registers.append(RegisterDescription());
739 // 0x040000EA: Unused
740 s_registers.append(RegisterDescription());
741 // 0x040000EC: Unused
742 s_registers.append(RegisterDescription());
743 // 0x040000EE: Unused
744 s_registers.append(RegisterDescription());
745 // 0x040000F0: Unused
746 s_registers.append(RegisterDescription());
747 // 0x040000F2: Unused
748 s_registers.append(RegisterDescription());
749 // 0x040000F4: Unused
750 s_registers.append(RegisterDescription());
751 // 0x040000F6: Unused
752 s_registers.append(RegisterDescription());
753 // 0x040000F8: Unused
754 s_registers.append(RegisterDescription());
755 // 0x040000FA: Unused
756 s_registers.append(RegisterDescription());
757 // 0x040000FC: Unused
758 s_registers.append(RegisterDescription());
759 // 0x040000FE: Unused
760 s_registers.append(RegisterDescription());
761 // 0x04000100: TM0CNT_LO
762 s_registers.append({
763 { tr("Value"), 0, 16 },
764 });
765 // 0x04000102: TM0CNT_HI
766 s_registers.append({
767 { tr("Scale"), 0, 2, {
768 tr("1"),
769 tr("1/64"),
770 tr("1/256"),
771 tr("1/1024"),
772 } },
773 { tr("IRQ"), 6 },
774 { tr("Enable"), 7 },
775 });
776 // 0x04000104: TM1CNT_LO
777 s_registers.append({
778 { tr("Value"), 0, 16 },
779 });
780 // 0x04000106: TM1CNT_HI
781 s_registers.append({
782 { tr("Scale"), 0, 2, {
783 tr("1"),
784 tr("1/64"),
785 tr("1/256"),
786 tr("1/1024"),
787 } },
788 { tr("Cascade"), 2 },
789 { tr("IRQ"), 6 },
790 { tr("Enable"), 7 },
791 });
792 // 0x04000108: TM2CNT_LO
793 s_registers.append({
794 { tr("Value"), 0, 16 },
795 });
796 // 0x0400010A: TM2CNT_HI
797 s_registers.append({
798 { tr("Scale"), 0, 2, {
799 tr("1"),
800 tr("1/64"),
801 tr("1/256"),
802 tr("1/1024"),
803 } },
804 { tr("Cascade"), 2 },
805 { tr("IRQ"), 6 },
806 { tr("Enable"), 7 },
807 });
808 // 0x0400010C: TM3CNT_LO
809 s_registers.append({
810 { tr("Value"), 0, 16 },
811 });
812 // 0x0400010E: TM3CNT_HI
813 s_registers.append({
814 { tr("Scale"), 0, 2, {
815 tr("1"),
816 tr("1/64"),
817 tr("1/256"),
818 tr("1/1024"),
819 } },
820 { tr("Cascade"), 2 },
821 { tr("IRQ"), 6 },
822 { tr("Enable"), 7 },
823 });
824 // 0x04000110: Unused
825 s_registers.append(RegisterDescription());
826 // 0x04000112: Unused
827 s_registers.append(RegisterDescription());
828 // 0x04000114: Unused
829 s_registers.append(RegisterDescription());
830 // 0x04000116: Unused
831 s_registers.append(RegisterDescription());
832 // 0x04000118: Unused
833 s_registers.append(RegisterDescription());
834 // 0x0400011A: Unused
835 s_registers.append(RegisterDescription());
836 // 0x0400011C: Unused
837 s_registers.append(RegisterDescription());
838 // 0x0400011E: Unused
839 s_registers.append(RegisterDescription());
840 // 0x04000120: SIOMULTI0
841 s_registers.append(RegisterDescription());
842 // 0x04000122: SIOMULTI1
843 s_registers.append(RegisterDescription());
844 // 0x04000124: SIOMULTI2
845 s_registers.append(RegisterDescription());
846 // 0x04000126: SIOMULTI3
847 s_registers.append(RegisterDescription());
848 // 0x04000128: SIOCNT
849 s_registers.append(RegisterDescription());
850 // 0x0400012A: SIOMLT_SEND
851 s_registers.append(RegisterDescription());
852 // 0x0400012C: Unused
853 s_registers.append(RegisterDescription());
854 // 0x0400012E: Unused
855 s_registers.append(RegisterDescription());
856 // 0x04000130: KEYINPUT
857 s_registers.append({
858 { tr("A"), 0 },
859 { tr("B"), 1 },
860 { tr("Select"), 2 },
861 { tr("Start"), 3 },
862 { tr("Right"), 4 },
863 { tr("Left"), 5 },
864 { tr("Up"), 6 },
865 { tr("Down"), 7 },
866 { tr("R"), 8 },
867 { tr("L"), 9 },
868 });
869 // 0x04000132: KEYCNT
870 s_registers.append({
871 { tr("A"), 0 },
872 { tr("B"), 1 },
873 { tr("Select"), 2 },
874 { tr("Start"), 3 },
875 { tr("Right"), 4 },
876 { tr("Left"), 5 },
877 { tr("Up"), 6 },
878 { tr("Down"), 7 },
879 { tr("R"), 8 },
880 { tr("L"), 9 },
881 { tr("IRQ"), 14 },
882 { tr("Condition"), 15 },
883 });
884 // 0x04000134: RCNT
885 s_registers.append({
886 { tr("SC"), 0 },
887 { tr("SD"), 1 },
888 { tr("SI"), 2 },
889 { tr("SO"), 3 },
890 });
891 // 0x04000136: Unused
892 s_registers.append(RegisterDescription());
893 // 0x04000138: SIOCNT
894 s_registers.append(RegisterDescription());
895 // 0x0400013A: Unused
896 s_registers.append(RegisterDescription());
897 // 0x0400013C: Unused
898 s_registers.append(RegisterDescription());
899 // 0x0400013E: Unused
900 s_registers.append(RegisterDescription());
901 // 0x04000140: JOYCNT
902 s_registers.append(RegisterDescription());
903 // 0x04000142: Unused
904 s_registers.append(RegisterDescription());
905 // 0x04000144: Unused
906 s_registers.append(RegisterDescription());
907 // 0x04000146: Unused
908 s_registers.append(RegisterDescription());
909 // 0x04000148: Unused
910 s_registers.append(RegisterDescription());
911 // 0x0400014A: Unused
912 s_registers.append(RegisterDescription());
913 // 0x0400014C: Unused
914 s_registers.append(RegisterDescription());
915 // 0x0400014E: Unused
916 s_registers.append(RegisterDescription());
917 // 0x04000150: JOY_RECV_LO
918 s_registers.append(RegisterDescription());
919 // 0x04000152: JOY_RECV_HI
920 s_registers.append(RegisterDescription());
921 // 0x04000154: JOY_TRANS_LO
922 s_registers.append(RegisterDescription());
923 // 0x04000156: JOY_TRANS_HI
924 s_registers.append(RegisterDescription());
925 // 0x04000158: JOYSTAT
926 s_registers.append(RegisterDescription());
927 // 0x0400015A: Unused
928 s_registers.append(RegisterDescription());
929 // 0x0400015C: Unused
930 s_registers.append(RegisterDescription());
931 // 0x0400015E: Unused
932 s_registers.append(RegisterDescription());
933 for (int i = 0x160; i < 0x200; i += 2) {
934 // Unused
935 s_registers.append(RegisterDescription());
936 }
937 // 0x04000200: IE
938 s_registers.append({
939 { tr("VBlank"), 0 },
940 { tr("HBlank"), 1 },
941 { tr("VCounter"), 2 },
942 { tr("Timer 0"), 3 },
943 { tr("Timer 1"), 4 },
944 { tr("Timer 2"), 5 },
945 { tr("Timer 3"), 6 },
946 { tr("SIO"), 7 },
947 { tr("DMA 0"), 8 },
948 { tr("DMA 1"), 9 },
949 { tr("DMA 2"), 10 },
950 { tr("DMA 3"), 11 },
951 { tr("Keypad"), 12 },
952 { tr("Gamepak"), 13 },
953 });
954 // 0x04000202: IF
955 s_registers.append({
956 { tr("VBlank"), 0 },
957 { tr("HBlank"), 1 },
958 { tr("VCounter"), 2 },
959 { tr("Timer 0"), 3 },
960 { tr("Timer 1"), 4 },
961 { tr("Timer 2"), 5 },
962 { tr("Timer 3"), 6 },
963 { tr("SIO"), 7 },
964 { tr("DMA 0"), 8 },
965 { tr("DMA 1"), 9 },
966 { tr("DMA 2"), 10 },
967 { tr("DMA 3"), 11 },
968 { tr("Keypad"), 12 },
969 { tr("Gamepak"), 13 },
970 });
971 // 0x04000204: WAITCNT
972 s_registers.append({
973 { tr("SRAM wait"), 0, 2, {
974 tr("4"),
975 tr("3"),
976 tr("2"),
977 tr("8"),
978 } },
979 { tr("Cart 0 non-sequential"), 2, 2, {
980 tr("4"),
981 tr("3"),
982 tr("2"),
983 tr("8"),
984 } },
985 { tr("Cart 0 sequential"), 4, 1, {
986 tr("2"),
987 tr("1"),
988 } },
989 { tr("Cart 1 non-sequential"), 5, 2, {
990 tr("4"),
991 tr("3"),
992 tr("2"),
993 tr("8"),
994 } },
995 { tr("Cart 1 sequential"), 7, 1, {
996 tr("4"),
997 tr("1"),
998 } },
999 { tr("Cart 2 non-sequential"), 8, 2, {
1000 tr("4"),
1001 tr("3"),
1002 tr("2"),
1003 tr("8"),
1004 } },
1005 { tr("Cart 2 sequential"), 10, 1, {
1006 tr("8"),
1007 tr("1"),
1008 } },
1009 { tr("PHI terminal"), 11, 2, {
1010 tr("Disable"),
1011 tr("4.19MHz"),
1012 tr("8.38MHz"),
1013 tr("16.78MHz"),
1014 } },
1015 { tr("Gamepak prefetch"), 14 },
1016 });
1017 // 0x04000206: Unused
1018 s_registers.append(RegisterDescription());
1019 // 0x04000208: IME
1020 s_registers.append({
1021 { tr("Enable IRQs"), 0 },
1022 });
1023 return s_registers;
1024}
1025
1026IOViewer::IOViewer(GameController* controller, QWidget* parent)
1027 : QDialog(parent)
1028 , m_controller(controller)
1029{
1030 m_ui.setupUi(this);
1031
1032 for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
1033 const char* reg = GBAIORegisterNames[i];
1034 if (!reg) {
1035 continue;
1036 }
1037 m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
1038 }
1039
1040 const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
1041 m_ui.regValue->setFont(font);
1042
1043 connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
1044 connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
1045 connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
1046
1047 m_b[0] = m_ui.b0;
1048 m_b[1] = m_ui.b1;
1049 m_b[2] = m_ui.b2;
1050 m_b[3] = m_ui.b3;
1051 m_b[4] = m_ui.b4;
1052 m_b[5] = m_ui.b5;
1053 m_b[6] = m_ui.b6;
1054 m_b[7] = m_ui.b7;
1055 m_b[8] = m_ui.b8;
1056 m_b[9] = m_ui.b9;
1057 m_b[10] = m_ui.bA;
1058 m_b[11] = m_ui.bB;
1059 m_b[12] = m_ui.bC;
1060 m_b[13] = m_ui.bD;
1061 m_b[14] = m_ui.bE;
1062 m_b[15] = m_ui.bF;
1063
1064 for (int i = 0; i < 16; ++i) {
1065 connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
1066 }
1067
1068 selectRegister(0);
1069}
1070
1071void IOViewer::updateRegister() {
1072 m_value = 0;
1073 uint16_t value = 0;
1074 m_controller->threadInterrupt();
1075 if (m_controller->isLoaded()) {
1076 value = GBAView16(m_controller->thread()->cpu, BASE_IO | m_register);
1077 }
1078 m_controller->threadContinue();
1079
1080 for (int i = 0; i < 16; ++i) {
1081 m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
1082 }
1083 m_value = value;
1084 emit valueChanged();
1085}
1086
1087void IOViewer::bitFlipped() {
1088 m_value = 0;
1089 for (int i = 0; i < 16; ++i) {
1090 m_value |= m_b[i]->isChecked() << i;
1091 }
1092 m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
1093 emit valueChanged();
1094}
1095
1096void IOViewer::writeback() {
1097 m_controller->threadInterrupt();
1098 if (m_controller->isLoaded()) {
1099 GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
1100 }
1101 m_controller->threadContinue();
1102 updateRegister();
1103}
1104
1105void IOViewer::selectRegister(unsigned address) {
1106 m_register = address;
1107 QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
1108 if (box) {
1109 // I can't believe there isn't a real way to do this...
1110 while (!box->isEmpty()) {
1111 QLayoutItem* item = box->takeAt(0);
1112 if (item->widget()) {
1113 delete item->widget();
1114 }
1115 delete item;
1116 }
1117 } else {
1118 box = new QGridLayout;
1119 }
1120 if (registerDescriptions().count() > address >> 1) {
1121 // TODO: Remove the check when done filling in register information
1122 const RegisterDescription& description = registerDescriptions().at(address >> 1);
1123 int i = 0;
1124 for (const RegisterItem& ri : description) {
1125 QLabel* label = new QLabel(ri.description);
1126 box->addWidget(label, i, 0);
1127 if (ri.size == 1) {
1128 QCheckBox* check = new QCheckBox;
1129 check->setEnabled(!ri.readonly);
1130 box->addWidget(check, i, 1, Qt::AlignRight);
1131 connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
1132 connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
1133 } else if (ri.items.empty()) {
1134 QSpinBox* sbox = new QSpinBox;
1135 sbox->setEnabled(!ri.readonly);
1136 sbox->setMaximum((1 << ri.size) - 1);
1137 sbox->setAccelerated(true);
1138 box->addWidget(sbox, i, 1, Qt::AlignRight);
1139
1140 connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [sbox, this, &ri](int v) {
1141 for (int o = 0; o < ri.size; ++o) {
1142 bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1143 m_b[o + ri.start]->setChecked(v & (1 << o));
1144 m_b[o + ri.start]->blockSignals(signalsBlocked);
1145 }
1146 });
1147
1148 auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
1149 int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1150 bool signalsBlocked = sbox->blockSignals(true);
1151 sbox->setValue(v);
1152 sbox->blockSignals(signalsBlocked);
1153 });
1154 connect(sbox, &QObject::destroyed, [connection, this]() {
1155 this->disconnect(connection);
1156 });
1157 } else {
1158 QComboBox* cbox = new QComboBox;
1159 cbox->setEnabled(!ri.readonly);
1160 ++i;
1161 box->addWidget(cbox, i, 0, 1, 2, Qt::AlignRight);
1162 for (int o = 0; o < 1 << ri.size; ++o) {
1163 if (ri.items.at(o).isNull()) {
1164 continue;
1165 }
1166 cbox->addItem(ri.items.at(o), o);
1167 }
1168
1169 connect(cbox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [cbox, this, &ri](int index) {
1170 unsigned v = cbox->itemData(index).toUInt();
1171 for (int o = 0; o < ri.size; ++o) {
1172 bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1173 m_b[o + ri.start]->setChecked(v & (1 << o));
1174 m_b[o + ri.start]->blockSignals(signalsBlocked);
1175 }
1176 });
1177
1178 auto connection = connect(this, &IOViewer::valueChanged, [cbox, this, &ri]() {
1179 unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1180 for (int i = 0; i < 1 << ri.size; ++i) {
1181 if (cbox->itemData(i) == v) {
1182 cbox->setCurrentIndex(i);
1183 }
1184 }
1185 });
1186 connect(cbox, &QObject::destroyed, [connection, this]() {
1187 this->disconnect(connection);
1188 });
1189
1190 }
1191 ++i;
1192 }
1193 }
1194 m_ui.regDescription->setLayout(box);
1195 updateRegister();
1196}
1197
1198void IOViewer::selectRegister() {
1199 selectRegister(m_ui.regSelect->currentData().toUInt());
1200}
1201
1202void IOViewer::buttonPressed(QAbstractButton* button) {
1203 switch (m_ui.buttonBox->standardButton(button)) {
1204 case QDialogButtonBox::Reset:
1205 updateRegister();
1206 break;
1207 case QDialogButtonBox::Apply:
1208 writeback();
1209 break;
1210 default:
1211 break;
1212 }
1213}