all repos — mgba @ 2f1cb61d0197893cf0c3861e9130ff1aeaefb0b0

mGBA Game Boy Advance Emulator

src/platform/qt/IOViewer.cpp (view raw)

   1/* Copyright (c) 2013-2015 Jeffrey Pfau
   2 *
   3 * This Source Code Form is subject to the terms of the Mozilla Public
   4 * License, v. 2.0. If a copy of the MPL was not distributed with this
   5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6#include "IOViewer.h"
   7
   8#include "GameController.h"
   9
  10#include <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}