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
15#include "gba/io.h"
16
17using namespace QGBA;
18
19
20QList<IOViewer::RegisterDescription> IOViewer::s_registers;
21
22const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions() {
23 if (!s_registers.isEmpty()) {
24 return s_registers;
25 }
26 // 0x04000000: DISPCNT
27 s_registers.append({
28 { tr("Background mode"), 0, 3, {
29 tr("Mode 0: 4 tile layers"),
30 tr("Mode 1: 2 tile layers + 1 rotated/scaled tile layer"),
31 tr("Mode 2: 2 rotated/scaled tile layers"),
32 tr("Mode 3: Full 15-bit bitmap"),
33 tr("Mode 4: Full 8-bit bitmap"),
34 tr("Mode 5: Small 15-bit bitmap"),
35 QString(),
36 QString()
37 } },
38 { tr("CGB Mode"), 3, 1, true },
39 { tr("Frame select"), 4 },
40 { tr("Unlocked HBlank"), 5 },
41 { tr("Linear OBJ tile mapping"), 6 },
42 { tr("Force blank screen"), 7 },
43 { tr("Enable background 0"), 8 },
44 { tr("Enable background 1"), 9 },
45 { tr("Enable background 2"), 10 },
46 { tr("Enable background 3"), 11 },
47 { tr("Enable OBJ"), 12 },
48 { tr("Enable Window 0"), 13 },
49 { tr("Enable Window 1"), 14 },
50 { tr("Enable OBJ Window"), 15 },
51 });
52 // 0x04000002: Green swap (undocumented and unimplemented)
53 s_registers.append(RegisterDescription());
54 // 0x04000004: DISPSTAT
55 s_registers.append({
56 { tr("Currently in VBlank"), 0, 1, true },
57 { tr("Currently in HBlank"), 1, 1, true },
58 { tr("Currently in VCounter"), 2, 1, true },
59 { tr("Enable VBlank IRQ generation"), 3 },
60 { tr("Enable HBlank IRQ generation"), 4 },
61 { tr("Enable VCounter IRQ generation"), 5 },
62 { tr("VCounter scanline"), 8, 8 },
63 });
64 // 0x04000006: VCOUNT
65 s_registers.append({
66 { tr("Current scanline"), 0, 8, true },
67 });
68 // 0x04000008: BG0CNT
69 s_registers.append({
70 { tr("Priority"), 0, 2 },
71 { tr("Tile data base (* 16kB)"), 2, 2 },
72 { tr("Enable mosaic"), 6 },
73 { tr("Enable 256-color"), 7 },
74 { tr("Tile map base (* 2kB)"), 8, 5 },
75 { tr("Background dimensions"), 14, 2 },
76 });
77 // 0x0400000A: BG1CNT
78 s_registers.append({
79 { tr("Priority"), 0, 2 },
80 { tr("Tile data base (* 16kB)"), 2, 2 },
81 { tr("Enable mosaic"), 6 },
82 { tr("Enable 256-color"), 7 },
83 { tr("Tile map base (* 2kB)"), 8, 5 },
84 { tr("Background dimensions"), 14, 2 },
85 });
86 // 0x0400000C: BG2CNT
87 s_registers.append({
88 { tr("Priority"), 0, 2 },
89 { tr("Tile data base (* 16kB)"), 2, 2 },
90 { tr("Enable mosaic"), 6 },
91 { tr("Enable 256-color"), 7 },
92 { tr("Tile map base (* 2kB)"), 8, 5 },
93 { tr("Overflow wraps"), 13 },
94 { tr("Background dimensions"), 14, 2 },
95 });
96 // 0x0400000E: BG3CNT
97 s_registers.append({
98 { tr("Priority"), 0, 2 },
99 { tr("Tile data base (* 16kB)"), 2, 2 },
100 { tr("Enable mosaic"), 6 },
101 { tr("Enable 256-color"), 7 },
102 { tr("Tile map base (* 2kB)"), 8, 5 },
103 { tr("Overflow wraps"), 13 },
104 { tr("Background dimensions"), 14, 2 },
105 });
106 // 0x04000010: BG0HOFS
107 s_registers.append({
108 { tr("Horizontal offset"), 0, 9 },
109 });
110 // 0x04000012: BG0VOFS
111 s_registers.append({
112 { tr("Vertical offset"), 0, 9 },
113 });
114 // 0x04000014: BG1HOFS
115 s_registers.append({
116 { tr("Horizontal offset"), 0, 9 },
117 });
118 // 0x04000016: BG1VOFS
119 s_registers.append({
120 { tr("Vertical offset"), 0, 9 },
121 });
122 // 0x04000018: BG2HOFS
123 s_registers.append({
124 { tr("Horizontal offset"), 0, 9 },
125 });
126 // 0x0400001A: BG2VOFS
127 s_registers.append({
128 { tr("Vertical offset"), 0, 9 },
129 });
130 // 0x0400001C: BG3HOFS
131 s_registers.append({
132 { tr("Horizontal offset"), 0, 9 },
133 });
134 // 0x0400001E: BG3VOFS
135 s_registers.append({
136 { tr("Vertical offset"), 0, 9 },
137 });
138 // 0x04000020: BG2PA
139 s_registers.append({
140 { tr("Fractional part"), 0, 8 },
141 { tr("Integer part"), 8, 8 },
142 });
143 // 0x04000022: BG2PB
144 s_registers.append({
145 { tr("Fractional part"), 0, 8 },
146 { tr("Integer part"), 8, 8 },
147 });
148 // 0x04000024: BG2PC
149 s_registers.append({
150 { tr("Fractional part"), 0, 8 },
151 { tr("Integer part"), 8, 8 },
152 });
153 // 0x04000026: BG2PD
154 s_registers.append({
155 { tr("Fractional part"), 0, 8 },
156 { tr("Integer part"), 8, 8 },
157 });
158 // 0x04000028: BG2X_LO
159 s_registers.append({
160 { tr("Fractional part"), 0, 8 },
161 { tr("Integer part (bottom)"), 8, 8 },
162 });
163 // 0x0400002A: BG2X_HI
164 s_registers.append({
165 { tr("Integer part (top)"), 0, 12 },
166 });
167 // 0x0400002C: BG2Y_LO
168 s_registers.append({
169 { tr("Fractional part"), 0, 8 },
170 { tr("Integer part (bottom)"), 8, 8 },
171 });
172 // 0x0400002E: BG2Y_HI
173 s_registers.append({
174 { tr("Integer part (top)"), 0, 12 },
175 });
176 // 0x04000030: BG3PA
177 s_registers.append({
178 { tr("Fractional part"), 0, 8 },
179 { tr("Integer part"), 8, 8 },
180 });
181 // 0x04000032: BG3PB
182 s_registers.append({
183 { tr("Fractional part"), 0, 8 },
184 { tr("Integer part"), 8, 8 },
185 });
186 // 0x04000034: BG3PC
187 s_registers.append({
188 { tr("Fractional part"), 0, 8 },
189 { tr("Integer part"), 8, 8 },
190 });
191 // 0x04000036: BG3PD
192 s_registers.append({
193 { tr("Fractional part"), 0, 8 },
194 { tr("Integer part"), 8, 8 },
195 });
196 // 0x04000038: BG3X_LO
197 s_registers.append({
198 { tr("Fractional part"), 0, 8 },
199 { tr("Integer part (bottom)"), 8, 8 },
200 });
201 // 0x0400003A: BG3X_HI
202 s_registers.append({
203 { tr("Integer part (top)"), 0, 12 },
204 });
205 // 0x0400003C: BG3Y_LO
206 s_registers.append({
207 { tr("Fractional part"), 0, 8 },
208 { tr("Integer part (bottom)"), 8, 8 },
209 });
210 // 0x0400003E: BG3Y_HI
211 s_registers.append({
212 { tr("Integer part (top)"), 0, 12 },
213 });
214 // 0x04000040: WIN0H
215 s_registers.append({
216 { tr("End x"), 0, 8 },
217 { tr("Start x"), 8, 8 },
218 });
219 // 0x04000042: WIN1H
220 s_registers.append({
221 { tr("End x"), 0, 8 },
222 { tr("Start x"), 8, 8 },
223 });
224 // 0x04000044: WIN0V
225 s_registers.append({
226 { tr("End y"), 0, 8 },
227 { tr("Start y"), 8, 8 },
228 });
229 // 0x04000046: WIN1V
230 s_registers.append({
231 { tr("End y"), 0, 8 },
232 { tr("Start y"), 8, 8 },
233 });
234 // 0x04000048: WININ
235 s_registers.append({
236 { tr("Window 0 enable BG 0"), 0 },
237 { tr("Window 0 enable BG 1"), 1 },
238 { tr("Window 0 enable BG 2"), 2 },
239 { tr("Window 0 enable BG 3"), 3 },
240 { tr("Window 0 enable OBJ"), 4 },
241 { tr("Window 0 enable blend"), 5 },
242 { tr("Window 1 enable BG 0"), 8 },
243 { tr("Window 1 enable BG 1"), 9 },
244 { tr("Window 1 enable BG 2"), 10 },
245 { tr("Window 1 enable BG 3"), 11 },
246 { tr("Window 1 enable OBJ"), 12 },
247 { tr("Window 1 enable blend"), 13 },
248 });
249 // 0x0400004A: WINOUT
250 s_registers.append({
251 { tr("Outside window enable BG 0"), 0 },
252 { tr("Outside window enable BG 1"), 1 },
253 { tr("Outside window enable BG 2"), 2 },
254 { tr("Outside window enable BG 3"), 3 },
255 { tr("Outside window enable OBJ"), 4 },
256 { tr("Outside window enable blend"), 5 },
257 { tr("OBJ window enable BG 0"), 8 },
258 { tr("OBJ window enable BG 1"), 9 },
259 { tr("OBJ window enable BG 2"), 10 },
260 { tr("OBJ window enable BG 3"), 11 },
261 { tr("OBJ window enable OBJ"), 12 },
262 { tr("OBJ window enable blend"), 13 },
263 });
264 // 0x0400004C: MOSAIC
265 s_registers.append({
266 { tr("Background mosaic size vertical"), 0, 4 },
267 { tr("Background mosaic size horizontal"), 4, 4 },
268 { tr("Object mosaic size vertical"), 8, 4 },
269 { tr("Object mosaic size horizontal"), 12, 4 },
270 });
271 // 0x0400004E: Unused
272 s_registers.append(RegisterDescription());
273 // 0x04000050: BLDCNT
274 s_registers.append({
275 { tr("BG 0 target 1"), 0 },
276 { tr("BG 1 target 1"), 1 },
277 { tr("BG 2 target 1"), 2 },
278 { tr("BG 3 target 1"), 3 },
279 { tr("OBJ target 1"), 4 },
280 { tr("Backdrop target 1"), 5 },
281 { tr("Blend mode"), 6, 2, {
282 tr("Disabled"),
283 tr("Additive blending"),
284 tr("Brighten"),
285 tr("Darken"),
286 } },
287 { tr("BG 0 target 2"), 8 },
288 { tr("BG 1 target 2"), 9 },
289 { tr("BG 2 target 2"), 10 },
290 { tr("BG 3 target 2"), 11 },
291 { tr("OBJ target 2"), 12 },
292 { tr("Backdrop target 2"), 13 },
293 });
294 // 0x04000052: BLDALPHA
295 s_registers.append({
296 { tr("Blend A (target 1)"), 0, 5 },
297 { tr("Blend B (target 2)"), 8, 5 },
298 });
299 // 0x04000054: BLDY
300 s_registers.append({
301 { tr("Blend Y"), 0, 5 },
302 });
303 // 0x04000056: Unused
304 s_registers.append(RegisterDescription());
305 // 0x04000058: Unused
306 s_registers.append(RegisterDescription());
307 // 0x0400005A: Unused
308 s_registers.append(RegisterDescription());
309 // 0x0400005C: Unused
310 s_registers.append(RegisterDescription());
311 // 0x0400005E: Unused
312 s_registers.append(RegisterDescription());
313 // 0x04000060: SOUND1CNT_LO
314 s_registers.append({
315 { tr("Sweep shifts"), 0, 3 },
316 { tr("Sweep subtract"), 3 },
317 { tr("Sweep time (in 1/128s)"), 4, 3 },
318 });
319 // 0x04000062: SOUND1CNT_HI
320 s_registers.append({
321 { tr("Sound length"), 0, 6 },
322 { tr("Duty cycle"), 6, 2 },
323 { tr("Envelope step time"), 8, 3 },
324 { tr("Envelope increase"), 11 },
325 { tr("Initial volume"), 12, 4 },
326 });
327 // 0x04000064: SOUND1CNT_X
328 s_registers.append({
329 { tr("Sound frequency"), 0, 11 },
330 { tr("Timed"), 14 },
331 { tr("Reset"), 15 },
332 });
333 // 0x04000066: Unused
334 s_registers.append(RegisterDescription());
335 // 0x04000068: SOUND2CNT_LO
336 s_registers.append({
337 { tr("Sound length"), 0, 6 },
338 { tr("Duty cycle"), 6, 2 },
339 { tr("Envelope step time"), 8, 3 },
340 { tr("Envelope increase"), 11 },
341 { tr("Initial volume"), 12, 4 },
342 });
343 // 0x0400006A: Unused
344 s_registers.append(RegisterDescription());
345 // 0x0400006C: SOUND2CNT_HI
346 s_registers.append({
347 { tr("Sound frequency"), 0, 11 },
348 { tr("Timed"), 14 },
349 { tr("Reset"), 15 },
350 });
351 // 0x0400006E: Unused
352 s_registers.append(RegisterDescription());
353 // 0x04000070: SOUND3CNT_LO
354 s_registers.append({
355 { tr("Double-size wave table"), 5 },
356 { tr("Active wave table"), 6 },
357 { tr("Enable channel 3"), 7 },
358 });
359 // 0x04000072: SOUND3CNT_HI
360 s_registers.append({
361 { tr("Sound length"), 0, 8 },
362 { tr("Volume"), 13, 3, {
363 tr("0%"),
364 tr("100%"),
365 tr("50%"),
366 tr("25%"),
367 tr("75%"),
368 tr("75%"),
369 tr("75%"),
370 tr("75%")
371 } },
372 });
373 // 0x04000074: SOUND3CNT_X
374 s_registers.append({
375 { tr("Sound frequency"), 0, 11 },
376 { tr("Timed"), 14 },
377 { tr("Reset"), 15 },
378 });
379 // 0x04000076: Unused
380 s_registers.append(RegisterDescription());
381 // 0x04000078: SOUND4CNT_LO
382 s_registers.append({
383 { tr("Sound length"), 0, 6 },
384 { tr("Envelope step time"), 8, 3 },
385 { tr("Envelope increase"), 11 },
386 { tr("Initial volume"), 12, 4 },
387 });
388 // 0x0400007A: Unused
389 s_registers.append(RegisterDescription());
390 // 0x0400007C: SOUND4CNT_HI
391 s_registers.append({
392 { tr("Clock divider"), 0, 3 },
393 { tr("Register stages"), 3, 1, {
394 tr("15"),
395 tr("7"),
396 } },
397 { tr("Shifter frequency"), 4, 4 },
398 { tr("Timed"), 14 },
399 { tr("Reset"), 15 },
400 });
401 // 0x0400007E: Unused
402 s_registers.append(RegisterDescription());
403 // 0x04000080: SOUNDCNT_LO
404 s_registers.append({
405 { tr("PSG volume right"), 0, 3 },
406 { tr("PSG volume left"), 4, 3 },
407 { tr("Enable channel 1 right"), 8 },
408 { tr("Enable channel 2 right"), 9 },
409 { tr("Enable channel 3 right"), 10 },
410 { tr("Enable channel 4 right"), 11 },
411 { tr("Enable channel 1 left"), 12 },
412 { tr("Enable channel 2 left"), 13 },
413 { tr("Enable channel 3 left"), 14 },
414 { tr("Enable channel 4 left"), 15 },
415 });
416 // 0x04000082: SOUNDCNT_HI
417 s_registers.append({
418 { tr("PSG master volume"), 0, 2, {
419 tr("25%"),
420 tr("50%"),
421 tr("100%"),
422 QString()
423 } },
424 { tr("Loud channel A"), 2 },
425 { tr("Loud channel B"), 3 },
426 { tr("Enable channel A right"), 8 },
427 { tr("Enable channel A left"), 9 },
428 { tr("Channel A timer"), 10, 1, {
429 tr("0"),
430 tr("1"),
431 } },
432 { tr("Channel A reset"), 11 },
433 { tr("Enable channel B right"), 12 },
434 { tr("Enable channel B left"), 13 },
435 { tr("Channel B timer"), 14, 1, {
436 tr("0"),
437 tr("1"),
438 } },
439 { tr("Channel B reset"), 15 },
440 });
441 // 0x04000084: SOUNDCNT_LO
442 s_registers.append({
443 { tr("Active channel 1"), 0, 1, true },
444 { tr("Active channel 2"), 1, 1, true },
445 { tr("Active channel 3"), 2, 1, true },
446 { tr("Active channel 4"), 3, 1, true },
447 { tr("Enable audio"), 7 },
448 });
449 // 0x04000086: Unused
450 s_registers.append(RegisterDescription());
451 // 0x04000088: SOUNDBIAS
452 s_registers.append({
453 { tr("Bias"), 0, 10 },
454 { tr("Resolution"), 14, 2 },
455 });
456 // 0x0400008A: Unused
457 s_registers.append(RegisterDescription());
458 // 0x0400008C: Unused
459 s_registers.append(RegisterDescription());
460 // 0x0400008E: Unused
461 s_registers.append(RegisterDescription());
462 // 0x04000090: WAVE_RAM0_LO
463 s_registers.append({
464 { tr("Sample"), 0, 4 },
465 { tr("Sample"), 4, 4 },
466 { tr("Sample"), 8, 4 },
467 { tr("Sample"), 12, 4 },
468 });
469 // 0x04000092: WAVE_RAM0_HI
470 s_registers.append({
471 { tr("Sample"), 0, 4 },
472 { tr("Sample"), 4, 4 },
473 { tr("Sample"), 8, 4 },
474 { tr("Sample"), 12, 4 },
475 });
476 // 0x04000094: WAVE_RAM1_LO
477 s_registers.append({
478 { tr("Sample"), 0, 4 },
479 { tr("Sample"), 4, 4 },
480 { tr("Sample"), 8, 4 },
481 { tr("Sample"), 12, 4 },
482 });
483 // 0x04000096: WAVE_RAM1_HI
484 s_registers.append({
485 { tr("Sample"), 0, 4 },
486 { tr("Sample"), 4, 4 },
487 { tr("Sample"), 8, 4 },
488 { tr("Sample"), 12, 4 },
489 });
490 // 0x04000098: WAVE_RAM2_LO
491 s_registers.append({
492 { tr("Sample"), 0, 4 },
493 { tr("Sample"), 4, 4 },
494 { tr("Sample"), 8, 4 },
495 { tr("Sample"), 12, 4 },
496 });
497 // 0x0400009A: WAVE_RAM2_HI
498 s_registers.append({
499 { tr("Sample"), 0, 4 },
500 { tr("Sample"), 4, 4 },
501 { tr("Sample"), 8, 4 },
502 { tr("Sample"), 12, 4 },
503 });
504 // 0x0400009C: WAVE_RAM3_LO
505 s_registers.append({
506 { tr("Sample"), 0, 4 },
507 { tr("Sample"), 4, 4 },
508 { tr("Sample"), 8, 4 },
509 { tr("Sample"), 12, 4 },
510 });
511 // 0x0400009E: WAVE_RAM0_HI
512 s_registers.append({
513 { tr("Sample"), 0, 4 },
514 { tr("Sample"), 4, 4 },
515 { tr("Sample"), 8, 4 },
516 { tr("Sample"), 12, 4 },
517 });
518 // 0x040000A0: FIFO_A_LO
519 s_registers.append({
520 { tr("Sample"), 0, 8 },
521 { tr("Sample"), 8, 8 },
522 });
523 // 0x040000A2: FIFO_A_HI
524 s_registers.append({
525 { tr("Sample"), 0, 8 },
526 { tr("Sample"), 8, 8 },
527 });
528 // 0x040000A4: FIFO_B_LO
529 s_registers.append({
530 { tr("Sample"), 0, 8 },
531 { tr("Sample"), 8, 8 },
532 });
533 // 0x040000A6: FIFO_B_HI
534 s_registers.append({
535 { tr("Sample"), 0, 8 },
536 { tr("Sample"), 8, 8 },
537 });
538 // 0x040000A8: Unused
539 s_registers.append(RegisterDescription());
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 // 0x040000B0: DMA0SAD_LO
547 s_registers.append({
548 { tr("Address (bottom)"), 0, 16 },
549 });
550 // 0x040000B2: DMA0SAD_HI
551 s_registers.append({
552 { tr("Address (top)"), 0, 16 },
553 });
554 // 0x040000B4: DMA0DAD_LO
555 s_registers.append({
556 { tr("Address (bottom)"), 0, 16 },
557 });
558 // 0x040000B6: DMA0DAD_HI
559 s_registers.append({
560 { tr("Address (top)"), 0, 16 },
561 });
562 // 0x040000B8: DMA0CNT_LO
563 s_registers.append({
564 { tr("Word count"), 0, 16 },
565 });
566 // 0x040000BA: DMA0CNT_HI
567 s_registers.append({
568 { tr("Destination offset"), 5, 2, {
569 tr("Increment"),
570 tr("Decrement"),
571 tr("Fixed"),
572 tr("Increment and reload"),
573 } },
574 { tr("Source offset"), 7, 2, {
575 tr("Increment"),
576 tr("Decrement"),
577 tr("Fixed"),
578 QString(),
579 } },
580 { tr("Repeat"), 9 },
581 { tr("32-bit"), 10 },
582 { tr("Start timing"), 12, 2, {
583 tr("Immediate"),
584 tr("VBlank"),
585 tr("HBlank"),
586 QString(),
587 } },
588 { tr("IRQ"), 14 },
589 { tr("Enable"), 15 },
590 });
591 // 0x040000BC: DMA1SAD_LO
592 s_registers.append({
593 { tr("Address (bottom)"), 0, 16 },
594 });
595 // 0x040000BE: DMA1SAD_HI
596 s_registers.append({
597 { tr("Address (top)"), 0, 16 },
598 });
599 // 0x040000C0: DMA1DAD_LO
600 s_registers.append({
601 { tr("Address (bottom)"), 0, 16 },
602 });
603 // 0x040000C2: DMA1DAD_HI
604 s_registers.append({
605 { tr("Address (top)"), 0, 16 },
606 });
607 // 0x040000C4: DMA1CNT_LO
608 s_registers.append({
609 { tr("Word count"), 0, 16 },
610 });
611 // 0x040000C6: DMA1CNT_HI
612 s_registers.append({
613 { tr("Destination offset"), 5, 2, {
614 tr("Increment"),
615 tr("Decrement"),
616 tr("Fixed"),
617 tr("Increment and reload"),
618 } },
619 { tr("Source offset"), 7, 2, {
620 tr("Increment"),
621 tr("Decrement"),
622 tr("Fixed"),
623 QString(),
624 } },
625 { tr("Repeat"), 9 },
626 { tr("32-bit"), 10 },
627 { tr("Start timing"), 12, 2, {
628 tr("Immediate"),
629 tr("VBlank"),
630 tr("HBlank"),
631 tr("Audio FIFO"),
632 } },
633 { tr("IRQ"), 14 },
634 { tr("Enable"), 15 },
635 });
636 // 0x040000C8: DMA2SAD_LO
637 s_registers.append({
638 { tr("Address (bottom)"), 0, 16 },
639 });
640 // 0x040000CA: DMA2SAD_HI
641 s_registers.append({
642 { tr("Address (top)"), 0, 16 },
643 });
644 // 0x040000CC: DMA2DAD_LO
645 s_registers.append({
646 { tr("Address (bottom)"), 0, 16 },
647 });
648 // 0x040000CE: DMA2DAD_HI
649 s_registers.append({
650 { tr("Address (top)"), 0, 16 },
651 });
652 // 0x040000D0: DMA2CNT_LO
653 s_registers.append({
654 { tr("Word count"), 0, 16 },
655 });
656 // 0x040000D2: DMA2CNT_HI
657 s_registers.append({
658 { tr("Destination offset"), 5, 2, {
659 tr("Increment"),
660 tr("Decrement"),
661 tr("Fixed"),
662 tr("Increment and reload"),
663 } },
664 { tr("Source offset"), 7, 2, {
665 tr("Increment"),
666 tr("Decrement"),
667 tr("Fixed"),
668 QString(),
669 } },
670 { tr("Repeat"), 9 },
671 { tr("32-bit"), 10 },
672 { tr("Start timing"), 12, 2, {
673 tr("Immediate"),
674 tr("VBlank"),
675 tr("HBlank"),
676 tr("Audio FIFO"),
677 } },
678 { tr("IRQ"), 14 },
679 { tr("Enable"), 15 },
680 });
681 // 0x040000D4: DMA3SAD_LO
682 s_registers.append({
683 { tr("Address (bottom)"), 0, 16 },
684 });
685 // 0x040000D6: DMA3SAD_HI
686 s_registers.append({
687 { tr("Address (top)"), 0, 16 },
688 });
689 // 0x040000D8: DMA3DAD_LO
690 s_registers.append({
691 { tr("Address (bottom)"), 0, 16 },
692 });
693 // 0x040000DA: DMA3DAD_HI
694 s_registers.append({
695 { tr("Address (top)"), 0, 16 },
696 });
697 // 0x040000DC: DMA3CNT_LO
698 s_registers.append({
699 { tr("Word count"), 0, 16 },
700 });
701 // 0x040000DE: DMA3CNT_HI
702 s_registers.append({
703 { tr("Destination offset"), 5, 2, {
704 tr("Increment"),
705 tr("Decrement"),
706 tr("Fixed"),
707 tr("Increment and reload"),
708 } },
709 { tr("Source offset"), 7, 2, {
710 tr("Increment"),
711 tr("Decrement"),
712 tr("Fixed"),
713 tr("Video Capture"),
714 } },
715 { tr("DRQ"), 8 },
716 { tr("Repeat"), 9 },
717 { tr("32-bit"), 10 },
718 { tr("Start timing"), 12, 2, {
719 tr("Immediate"),
720 tr("VBlank"),
721 tr("HBlank"),
722 tr("Audio FIFO"),
723 } },
724 { tr("IRQ"), 14 },
725 { tr("Enable"), 15 },
726 });
727 // 0x040000E0: Unused
728 s_registers.append(RegisterDescription());
729 // 0x040000E2: Unused
730 s_registers.append(RegisterDescription());
731 // 0x040000E4: Unused
732 s_registers.append(RegisterDescription());
733 // 0x040000E6: Unused
734 s_registers.append(RegisterDescription());
735 // 0x040000E8: Unused
736 s_registers.append(RegisterDescription());
737 // 0x040000EA: Unused
738 s_registers.append(RegisterDescription());
739 // 0x040000EC: Unused
740 s_registers.append(RegisterDescription());
741 // 0x040000EE: Unused
742 s_registers.append(RegisterDescription());
743 // 0x040000F0: Unused
744 s_registers.append(RegisterDescription());
745 // 0x040000F2: Unused
746 s_registers.append(RegisterDescription());
747 // 0x040000F4: Unused
748 s_registers.append(RegisterDescription());
749 // 0x040000F6: Unused
750 s_registers.append(RegisterDescription());
751 // 0x040000F8: Unused
752 s_registers.append(RegisterDescription());
753 // 0x040000FA: Unused
754 s_registers.append(RegisterDescription());
755 // 0x040000FC: Unused
756 s_registers.append(RegisterDescription());
757 // 0x040000FE: Unused
758 s_registers.append(RegisterDescription());
759 // 0x04000100: TM0CNT_LO
760 s_registers.append({
761 { tr("Value"), 0, 16 },
762 });
763 // 0x04000102: TM0CNT_HI
764 s_registers.append({
765 { tr("Scale"), 0, 2, {
766 tr("1"),
767 tr("1/64"),
768 tr("1/256"),
769 tr("1/1024"),
770 } },
771 { tr("IRQ"), 6 },
772 { tr("Enable"), 7 },
773 });
774 // 0x04000104: TM1CNT_LO
775 s_registers.append({
776 { tr("Value"), 0, 16 },
777 });
778 // 0x04000106: TM1CNT_HI
779 s_registers.append({
780 { tr("Scale"), 0, 2, {
781 tr("1"),
782 tr("1/64"),
783 tr("1/256"),
784 tr("1/1024"),
785 } },
786 { tr("Cascade"), 2 },
787 { tr("IRQ"), 6 },
788 { tr("Enable"), 7 },
789 });
790 // 0x04000108: TM2CNT_LO
791 s_registers.append({
792 { tr("Value"), 0, 16 },
793 });
794 // 0x0400010A: TM2CNT_HI
795 s_registers.append({
796 { tr("Scale"), 0, 2, {
797 tr("1"),
798 tr("1/64"),
799 tr("1/256"),
800 tr("1/1024"),
801 } },
802 { tr("Cascade"), 2 },
803 { tr("IRQ"), 6 },
804 { tr("Enable"), 7 },
805 });
806 // 0x0400010C: TM3CNT_LO
807 s_registers.append({
808 { tr("Value"), 0, 16 },
809 });
810 // 0x0400010E: TM3CNT_HI
811 s_registers.append({
812 { tr("Scale"), 0, 2, {
813 tr("1"),
814 tr("1/64"),
815 tr("1/256"),
816 tr("1/1024"),
817 } },
818 { tr("Cascade"), 2 },
819 { tr("IRQ"), 6 },
820 { tr("Enable"), 7 },
821 });
822 // 0x04000110: Unused
823 s_registers.append(RegisterDescription());
824 // 0x04000112: Unused
825 s_registers.append(RegisterDescription());
826 // 0x04000114: Unused
827 s_registers.append(RegisterDescription());
828 // 0x04000116: Unused
829 s_registers.append(RegisterDescription());
830 // 0x04000118: Unused
831 s_registers.append(RegisterDescription());
832 // 0x0400011A: Unused
833 s_registers.append(RegisterDescription());
834 // 0x0400011C: Unused
835 s_registers.append(RegisterDescription());
836 // 0x0400011E: Unused
837 s_registers.append(RegisterDescription());
838 // 0x04000120: SIOMULTI0
839 s_registers.append(RegisterDescription());
840 // 0x04000122: SIOMULTI1
841 s_registers.append(RegisterDescription());
842 // 0x04000124: SIOMULTI2
843 s_registers.append(RegisterDescription());
844 // 0x04000126: SIOMULTI3
845 s_registers.append(RegisterDescription());
846 // 0x04000128: SIOCNT
847 s_registers.append(RegisterDescription());
848 // 0x0400012A: SIOMLT_SEND
849 s_registers.append(RegisterDescription());
850 // 0x0400012C: Unused
851 s_registers.append(RegisterDescription());
852 // 0x0400012E: Unused
853 s_registers.append(RegisterDescription());
854 // 0x04000130: KEYINPUT
855 s_registers.append({
856 { tr("A"), 0 },
857 { tr("B"), 1 },
858 { tr("Select"), 2 },
859 { tr("Start"), 3 },
860 { tr("Right"), 4 },
861 { tr("Left"), 5 },
862 { tr("Up"), 6 },
863 { tr("Down"), 7 },
864 { tr("R"), 8 },
865 { tr("L"), 9 },
866 });
867 // 0x04000132: KEYCNT
868 s_registers.append({
869 { tr("A"), 0 },
870 { tr("B"), 1 },
871 { tr("Select"), 2 },
872 { tr("Start"), 3 },
873 { tr("Right"), 4 },
874 { tr("Left"), 5 },
875 { tr("Up"), 6 },
876 { tr("Down"), 7 },
877 { tr("R"), 8 },
878 { tr("L"), 9 },
879 { tr("IRQ"), 14 },
880 { tr("Condition"), 15 },
881 });
882 // 0x04000134: RCNT
883 s_registers.append({
884 { tr("SC"), 0 },
885 { tr("SD"), 1 },
886 { tr("SI"), 2 },
887 { tr("SO"), 3 },
888 });
889 // 0x04000136: Unused
890 s_registers.append(RegisterDescription());
891 // 0x04000138: SIOCNT
892 s_registers.append(RegisterDescription());
893 // 0x0400013A: Unused
894 s_registers.append(RegisterDescription());
895 // 0x0400013C: Unused
896 s_registers.append(RegisterDescription());
897 // 0x0400013E: Unused
898 s_registers.append(RegisterDescription());
899 // 0x04000140: JOYCNT
900 s_registers.append(RegisterDescription());
901 // 0x04000142: Unused
902 s_registers.append(RegisterDescription());
903 // 0x04000144: Unused
904 s_registers.append(RegisterDescription());
905 // 0x04000146: Unused
906 s_registers.append(RegisterDescription());
907 // 0x04000148: Unused
908 s_registers.append(RegisterDescription());
909 // 0x0400014A: Unused
910 s_registers.append(RegisterDescription());
911 // 0x0400014C: Unused
912 s_registers.append(RegisterDescription());
913 // 0x0400014E: Unused
914 s_registers.append(RegisterDescription());
915 // 0x04000150: JOY_RECV_LO
916 s_registers.append(RegisterDescription());
917 // 0x04000152: JOY_RECV_HI
918 s_registers.append(RegisterDescription());
919 // 0x04000154: JOY_TRANS_LO
920 s_registers.append(RegisterDescription());
921 // 0x04000156: JOY_TRANS_HI
922 s_registers.append(RegisterDescription());
923 // 0x04000158: JOYSTAT
924 s_registers.append(RegisterDescription());
925 // 0x0400015A: Unused
926 s_registers.append(RegisterDescription());
927 // 0x0400015C: Unused
928 s_registers.append(RegisterDescription());
929 // 0x0400015E: Unused
930 s_registers.append(RegisterDescription());
931 for (int i = 0x160; i < 0x200; i += 2) {
932 // Unused
933 s_registers.append(RegisterDescription());
934 }
935 // 0x04000200: IE
936 s_registers.append({
937 { tr("VBlank"), 0 },
938 { tr("HBlank"), 1 },
939 { tr("VCounter"), 2 },
940 { tr("Timer 0"), 3 },
941 { tr("Timer 1"), 4 },
942 { tr("Timer 2"), 5 },
943 { tr("Timer 3"), 6 },
944 { tr("SIO"), 7 },
945 { tr("DMA 0"), 8 },
946 { tr("DMA 1"), 9 },
947 { tr("DMA 2"), 10 },
948 { tr("DMA 3"), 11 },
949 { tr("Keypad"), 12 },
950 { tr("Gamepak"), 13 },
951 });
952 // 0x04000202: IF
953 s_registers.append({
954 { tr("VBlank"), 0 },
955 { tr("HBlank"), 1 },
956 { tr("VCounter"), 2 },
957 { tr("Timer 0"), 3 },
958 { tr("Timer 1"), 4 },
959 { tr("Timer 2"), 5 },
960 { tr("Timer 3"), 6 },
961 { tr("SIO"), 7 },
962 { tr("DMA 0"), 8 },
963 { tr("DMA 1"), 9 },
964 { tr("DMA 2"), 10 },
965 { tr("DMA 3"), 11 },
966 { tr("Keypad"), 12 },
967 { tr("Gamepak"), 13 },
968 });
969 // 0x04000204: WAITCNT
970 s_registers.append({
971 { tr("SRAM wait"), 0, 2, {
972 tr("4"),
973 tr("3"),
974 tr("2"),
975 tr("8"),
976 } },
977 { tr("Cart 0 non-sequential"), 2, 2, {
978 tr("4"),
979 tr("3"),
980 tr("2"),
981 tr("8"),
982 } },
983 { tr("Cart 0 sequential"), 4, 1, {
984 tr("2"),
985 tr("1"),
986 } },
987 { tr("Cart 1 non-sequential"), 5, 2, {
988 tr("4"),
989 tr("3"),
990 tr("2"),
991 tr("8"),
992 } },
993 { tr("Cart 1 sequential"), 7, 1, {
994 tr("4"),
995 tr("1"),
996 } },
997 { tr("Cart 2 non-sequential"), 8, 2, {
998 tr("4"),
999 tr("3"),
1000 tr("2"),
1001 tr("8"),
1002 } },
1003 { tr("Cart 2 sequential"), 10, 1, {
1004 tr("8"),
1005 tr("1"),
1006 } },
1007 { tr("PHI terminal"), 11, 2, {
1008 tr("Disable"),
1009 tr("4.19MHz"),
1010 tr("8.38MHz"),
1011 tr("16.78MHz"),
1012 } },
1013 { tr("Gamepak prefetch"), 14 },
1014 });
1015 // 0x04000206: Unused
1016 s_registers.append(RegisterDescription());
1017 // 0x04000208: IME
1018 s_registers.append({
1019 { tr("Enable IRQs"), 0 },
1020 });
1021 return s_registers;
1022}
1023
1024IOViewer::IOViewer(GameController* controller, QWidget* parent)
1025 : QDialog(parent)
1026 , m_controller(controller)
1027{
1028 m_ui.setupUi(this);
1029
1030 for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
1031 const char* reg = GBAIORegisterNames[i];
1032 if (!reg) {
1033 continue;
1034 }
1035 m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
1036 }
1037
1038 const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
1039 m_ui.regValue->setFont(font);
1040
1041 connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
1042 connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
1043 connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
1044
1045 m_b[0] = m_ui.b0;
1046 m_b[1] = m_ui.b1;
1047 m_b[2] = m_ui.b2;
1048 m_b[3] = m_ui.b3;
1049 m_b[4] = m_ui.b4;
1050 m_b[5] = m_ui.b5;
1051 m_b[6] = m_ui.b6;
1052 m_b[7] = m_ui.b7;
1053 m_b[8] = m_ui.b8;
1054 m_b[9] = m_ui.b9;
1055 m_b[10] = m_ui.bA;
1056 m_b[11] = m_ui.bB;
1057 m_b[12] = m_ui.bC;
1058 m_b[13] = m_ui.bD;
1059 m_b[14] = m_ui.bE;
1060 m_b[15] = m_ui.bF;
1061
1062 for (int i = 0; i < 16; ++i) {
1063 connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
1064 }
1065
1066 selectRegister(0);
1067}
1068
1069void IOViewer::updateRegister() {
1070 m_value = 0;
1071 uint16_t value = 0;
1072 m_controller->threadInterrupt();
1073 if (m_controller->isLoaded()) {
1074 value = GBAView16(static_cast<ARMCore*>(m_controller->thread()->core->cpu), BASE_IO | m_register);
1075 }
1076 m_controller->threadContinue();
1077
1078 for (int i = 0; i < 16; ++i) {
1079 m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
1080 }
1081 m_value = value;
1082 emit valueChanged();
1083}
1084
1085void IOViewer::bitFlipped() {
1086 m_value = 0;
1087 for (int i = 0; i < 16; ++i) {
1088 m_value |= m_b[i]->isChecked() << i;
1089 }
1090 m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
1091 emit valueChanged();
1092}
1093
1094void IOViewer::writeback() {
1095 m_controller->threadInterrupt();
1096 if (m_controller->isLoaded()) {
1097 GBAIOWrite(static_cast<GBA*>(m_controller->thread()->core->board), m_register, m_value);
1098 }
1099 m_controller->threadContinue();
1100 updateRegister();
1101}
1102
1103void IOViewer::selectRegister(unsigned address) {
1104 m_register = address;
1105 QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
1106 if (box) {
1107 // I can't believe there isn't a real way to do this...
1108 while (!box->isEmpty()) {
1109 QLayoutItem* item = box->takeAt(0);
1110 if (item->widget()) {
1111 delete item->widget();
1112 }
1113 delete item;
1114 }
1115 } else {
1116 box = new QGridLayout;
1117 }
1118 if (registerDescriptions().count() > address >> 1) {
1119 // TODO: Remove the check when done filling in register information
1120 const RegisterDescription& description = registerDescriptions().at(address >> 1);
1121 int i = 0;
1122 for (const RegisterItem& ri : description) {
1123 QLabel* label = new QLabel(ri.description);
1124 box->addWidget(label, i, 0);
1125 if (ri.size == 1) {
1126 QCheckBox* check = new QCheckBox;
1127 check->setEnabled(!ri.readonly);
1128 box->addWidget(check, i, 1, Qt::AlignRight);
1129 connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
1130 connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
1131 } else if (ri.items.empty()) {
1132 QSpinBox* sbox = new QSpinBox;
1133 sbox->setEnabled(!ri.readonly);
1134 sbox->setMaximum((1 << ri.size) - 1);
1135 sbox->setAccelerated(true);
1136 box->addWidget(sbox, i, 1, Qt::AlignRight);
1137
1138 connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [sbox, this, &ri](int v) {
1139 for (int o = 0; o < ri.size; ++o) {
1140 bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1141 m_b[o + ri.start]->setChecked(v & (1 << o));
1142 m_b[o + ri.start]->blockSignals(signalsBlocked);
1143 }
1144 });
1145
1146 auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
1147 int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1148 bool signalsBlocked = sbox->blockSignals(true);
1149 sbox->setValue(v);
1150 sbox->blockSignals(signalsBlocked);
1151 });
1152 connect(sbox, &QObject::destroyed, [connection, this]() {
1153 this->disconnect(connection);
1154 });
1155 } else {
1156 QComboBox* cbox = new QComboBox;
1157 cbox->setEnabled(!ri.readonly);
1158 ++i;
1159 box->addWidget(cbox, i, 0, 1, 2, Qt::AlignRight);
1160 for (int o = 0; o < 1 << ri.size; ++o) {
1161 if (ri.items.at(o).isNull()) {
1162 continue;
1163 }
1164 cbox->addItem(ri.items.at(o), o);
1165 }
1166
1167 connect(cbox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [cbox, this, &ri](int index) {
1168 unsigned v = cbox->itemData(index).toUInt();
1169 for (int o = 0; o < ri.size; ++o) {
1170 bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1171 m_b[o + ri.start]->setChecked(v & (1 << o));
1172 m_b[o + ri.start]->blockSignals(signalsBlocked);
1173 }
1174 });
1175
1176 auto connection = connect(this, &IOViewer::valueChanged, [cbox, this, &ri]() {
1177 unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1178 for (int i = 0; i < 1 << ri.size; ++i) {
1179 if (cbox->itemData(i) == v) {
1180 cbox->setCurrentIndex(i);
1181 }
1182 }
1183 });
1184 connect(cbox, &QObject::destroyed, [connection, this]() {
1185 this->disconnect(connection);
1186 });
1187
1188 }
1189 ++i;
1190 }
1191 }
1192 m_ui.regDescription->setLayout(box);
1193 updateRegister();
1194}
1195
1196void IOViewer::selectRegister() {
1197 selectRegister(m_ui.regSelect->currentData().toUInt());
1198}
1199
1200void IOViewer::buttonPressed(QAbstractButton* button) {
1201 switch (m_ui.buttonBox->standardButton(button)) {
1202 case QDialogButtonBox::Reset:
1203 updateRegister();
1204 break;
1205 case QDialogButtonBox::Apply:
1206 writeback();
1207 break;
1208 default:
1209 break;
1210 }
1211}