all repos — mgba @ 9b0393d50ffcd27c487740098c3c0bb72b16529f

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 "CoreController.h"
   9
  10#include <QComboBox>
  11#include <QFontDatabase>
  12#include <QGridLayout>
  13#include <QSpinBox>
  14
  15#include <mgba/internal/gba/io.h>
  16#include <mgba/internal/gba/memory.h>
  17
  18struct ARMCore;
  19
  20using namespace QGBA;
  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(std::shared_ptr<CoreController> 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, &QDialogButtonBox::clicked, this, &IOViewer::buttonPressed);
1044	connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
1045	connect(m_ui.regSelect, &QComboBox::currentTextChanged,
1046	        this, static_cast<void (IOViewer::*)()>(&IOViewer::selectRegister));
1047
1048	m_b[0] = m_ui.b0;
1049	m_b[1] = m_ui.b1;
1050	m_b[2] = m_ui.b2;
1051	m_b[3] = m_ui.b3;
1052	m_b[4] = m_ui.b4;
1053	m_b[5] = m_ui.b5;
1054	m_b[6] = m_ui.b6;
1055	m_b[7] = m_ui.b7;
1056	m_b[8] = m_ui.b8;
1057	m_b[9] = m_ui.b9;
1058	m_b[10] = m_ui.bA;
1059	m_b[11] = m_ui.bB;
1060	m_b[12] = m_ui.bC;
1061	m_b[13] = m_ui.bD;
1062	m_b[14] = m_ui.bE;
1063	m_b[15] = m_ui.bF;
1064
1065	for (int i = 0; i < 16; ++i) {
1066		connect(m_b[i], &QAbstractButton::toggled, this, &IOViewer::bitFlipped);
1067	}
1068
1069	selectRegister(0);
1070
1071	connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
1072}
1073
1074void IOViewer::updateRegister() {
1075	m_value = 0;
1076	uint16_t value = 0;
1077	{
1078		CoreController::Interrupter interrupter(m_controller);
1079		value = GBAView16(static_cast<ARMCore*>(m_controller->thread()->core->cpu), BASE_IO | m_register);
1080	}
1081
1082	for (int i = 0; i < 16; ++i) {
1083		m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
1084	}
1085	m_value = value;
1086	emit valueChanged();
1087}
1088
1089void IOViewer::bitFlipped() {
1090	m_value = 0;
1091	for (int i = 0; i < 16; ++i) {
1092		m_value |= m_b[i]->isChecked() << i;
1093	}
1094	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
1095	emit valueChanged();
1096}
1097
1098void IOViewer::writeback() {
1099	{
1100		CoreController::Interrupter interrupter(m_controller);
1101		GBAIOWrite(static_cast<GBA*>(m_controller->thread()->core->board), m_register, m_value);
1102	}
1103	updateRegister();
1104}
1105
1106void IOViewer::selectRegister(unsigned address) {
1107	m_register = address;
1108	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
1109	if (box) {
1110		// I can't believe there isn't a real way to do this...
1111		while (!box->isEmpty()) {
1112			QLayoutItem* item = box->takeAt(0);
1113			if (item->widget()) {
1114				delete item->widget();
1115			}
1116			delete item;
1117		}
1118	} else {
1119		box = new QGridLayout;
1120	}
1121	if (registerDescriptions().count() > address >> 1) {
1122		// TODO: Remove the check when done filling in register information
1123		const RegisterDescription& description = registerDescriptions().at(address >> 1);
1124		int i = 0;
1125		for (const RegisterItem& ri : description) {
1126			QLabel* label = new QLabel(ri.description);
1127			box->addWidget(label, i, 0);
1128			if (ri.size == 1) {
1129				QCheckBox* check = new QCheckBox;
1130				check->setEnabled(!ri.readonly);
1131				box->addWidget(check, i, 1, Qt::AlignRight);
1132				connect(check, &QAbstractButton::toggled, m_b[ri.start], &QAbstractButton::setChecked);
1133				connect(m_b[ri.start], &QAbstractButton::toggled, check, &QAbstractButton::setChecked);
1134			} else if (ri.items.empty()) {
1135				QSpinBox* sbox = new QSpinBox;
1136				sbox->setEnabled(!ri.readonly);
1137				sbox->setMaximum((1 << ri.size) - 1);
1138				sbox->setAccelerated(true);
1139				box->addWidget(sbox, i, 1, Qt::AlignRight);
1140
1141				connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [sbox, this, &ri](int v) {
1142					for (int o = 0; o < ri.size; ++o) {
1143						bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1144						m_b[o + ri.start]->setChecked(v & (1 << o));
1145						m_b[o + ri.start]->blockSignals(signalsBlocked);
1146					}
1147				});
1148
1149				auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
1150					int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1151					bool signalsBlocked = sbox->blockSignals(true);
1152					sbox->setValue(v);
1153					sbox->blockSignals(signalsBlocked);
1154				});
1155				connect(sbox, &QObject::destroyed, [connection, this]() {
1156					this->disconnect(connection);
1157				});
1158			} else {
1159				QComboBox* cbox = new QComboBox;
1160				cbox->setEnabled(!ri.readonly);
1161				++i;
1162				box->addWidget(cbox, i, 0, 1, 2, Qt::AlignRight);
1163				for (int o = 0; o < 1 << ri.size; ++o) {
1164					if (ri.items.at(o).isNull()) {
1165						continue;
1166					}
1167					cbox->addItem(ri.items.at(o), o);
1168				}
1169
1170				connect(cbox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [cbox, this, &ri](int index) {
1171					unsigned v = cbox->itemData(index).toUInt();
1172					for (int o = 0; o < ri.size; ++o) {
1173						bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
1174						m_b[o + ri.start]->setChecked(v & (1 << o));
1175						m_b[o + ri.start]->blockSignals(signalsBlocked);
1176					}
1177				});
1178
1179				auto connection = connect(this, &IOViewer::valueChanged, [cbox, this, &ri]() {
1180					unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1181					for (int i = 0; i < 1 << ri.size; ++i) {
1182						if (cbox->itemData(i) == v) {
1183							cbox->setCurrentIndex(i);
1184						}
1185					}
1186				});
1187				connect(cbox, &QObject::destroyed, [connection, this]() {
1188					this->disconnect(connection);
1189				});
1190
1191			}
1192			++i;
1193		}
1194	}
1195	m_ui.regDescription->setLayout(box);
1196	updateRegister();
1197}
1198
1199void IOViewer::selectRegister() {
1200	selectRegister(m_ui.regSelect->currentData().toUInt());
1201}
1202
1203void IOViewer::buttonPressed(QAbstractButton* button) {
1204	switch (m_ui.buttonBox->standardButton(button)) {
1205	case QDialogButtonBox::Reset:
1206		updateRegister();
1207	 	break;
1208	case QDialogButtonBox::Apply:
1209	 	writeback();
1210	 	break;
1211	default:
1212		break;
1213	}
1214}