all repos — mgba @ 8c10d2f85708d70c1a393a58164eca73a8daed76

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#include "GBAApp.h"
  10
  11#include <QComboBox>
  12#include <QGridLayout>
  13#include <QSpinBox>
  14
  15#ifdef M_CORE_GBA
  16#include <mgba/internal/gba/io.h>
  17#include <mgba/internal/gba/memory.h>
  18#endif
  19
  20#ifdef M_CORE_GB
  21#include <mgba/internal/gb/io.h>
  22#include <mgba/internal/gb/memory.h>
  23#endif
  24
  25struct ARMCore;
  26
  27using namespace QGBA;
  28
  29QHash<mPlatform, QList<IOViewer::RegisterDescription>> IOViewer::s_registers;
  30
  31const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions(mPlatform platform) {
  32	if (!s_registers.isEmpty()) {
  33		return s_registers[platform];
  34	}
  35#ifdef M_CORE_GBA
  36	QList<IOViewer::RegisterDescription> regGBA;
  37	// 0x04000000: DISPCNT
  38	regGBA.append({
  39		{ tr("Background mode"), 0, 3, {
  40			tr("Mode 0: 4 tile layers"),
  41			tr("Mode 1: 2 tile layers + 1 rotated/scaled tile layer"),
  42			tr("Mode 2: 2 rotated/scaled tile layers"),
  43			tr("Mode 3: Full 15-bit bitmap"),
  44			tr("Mode 4: Full 8-bit bitmap"),
  45			tr("Mode 5: Small 15-bit bitmap"),
  46			QString(),
  47			QString()
  48		} },
  49		{ tr("CGB Mode"), 3, 1, true },
  50		{ tr("Frame select"), 4 },
  51		{ tr("Unlocked HBlank"), 5 },
  52		{ tr("Linear OBJ tile mapping"), 6 },
  53		{ tr("Force blank screen"), 7 },
  54		{ tr("Enable background 0"), 8 },
  55		{ tr("Enable background 1"), 9 },
  56		{ tr("Enable background 2"), 10 },
  57		{ tr("Enable background 3"), 11 },
  58		{ tr("Enable OBJ"), 12 },
  59		{ tr("Enable Window 0"), 13 },
  60		{ tr("Enable Window 1"), 14 },
  61		{ tr("Enable OBJ Window"), 15 },
  62	});
  63	// 0x04000002: Green swap
  64	regGBA.append({
  65		{ tr("Swap green components"), 0 },
  66	});
  67	// 0x04000004: DISPSTAT
  68	regGBA.append({
  69		{ tr("Currently in VBlank"), 0, 1, true },
  70		{ tr("Currently in HBlank"), 1, 1, true },
  71		{ tr("Currently in VCounter"), 2, 1, true },
  72		{ tr("Enable VBlank IRQ generation"), 3 },
  73		{ tr("Enable HBlank IRQ generation"), 4 },
  74		{ tr("Enable VCounter IRQ generation"), 5 },
  75		{ tr("VCounter scanline"), 8, 8 },
  76	});
  77	// 0x04000006: VCOUNT
  78	regGBA.append({
  79		{ tr("Current scanline"), 0, 8, true },
  80	});
  81	// 0x04000008: BG0CNT
  82	regGBA.append({
  83		{ tr("Priority"), 0, 2 },
  84		{ tr("Tile data base (* 16kB)"), 2, 2 },
  85		{ tr("Enable mosaic"), 6 },
  86		{ tr("Enable 256-color"), 7 },
  87		{ tr("Tile map base (* 2kB)"), 8, 5 },
  88		{ tr("Background dimensions"), 14, 2 },
  89	});
  90	// 0x0400000A: BG1CNT
  91	regGBA.append({
  92		{ tr("Priority"), 0, 2 },
  93		{ tr("Tile data base (* 16kB)"), 2, 2 },
  94		{ tr("Enable mosaic"), 6 },
  95		{ tr("Enable 256-color"), 7 },
  96		{ tr("Tile map base (* 2kB)"), 8, 5 },
  97		{ tr("Background dimensions"), 14, 2 },
  98	});
  99	// 0x0400000C: BG2CNT
 100	regGBA.append({
 101		{ tr("Priority"), 0, 2 },
 102		{ tr("Tile data base (* 16kB)"), 2, 2 },
 103		{ tr("Enable mosaic"), 6 },
 104		{ tr("Enable 256-color"), 7 },
 105		{ tr("Tile map base (* 2kB)"), 8, 5 },
 106		{ tr("Overflow wraps"), 13 },
 107		{ tr("Background dimensions"), 14, 2 },
 108	});
 109	// 0x0400000E: BG3CNT
 110	regGBA.append({
 111		{ tr("Priority"), 0, 2 },
 112		{ tr("Tile data base (* 16kB)"), 2, 2 },
 113		{ tr("Enable mosaic"), 6 },
 114		{ tr("Enable 256-color"), 7 },
 115		{ tr("Tile map base (* 2kB)"), 8, 5 },
 116		{ tr("Overflow wraps"), 13 },
 117		{ tr("Background dimensions"), 14, 2 },
 118	});
 119	// 0x04000010: BG0HOFS
 120	regGBA.append({
 121		{ tr("Horizontal offset"), 0, 9 },
 122	});
 123	// 0x04000012: BG0VOFS
 124	regGBA.append({
 125		{ tr("Vertical offset"), 0, 9 },
 126	});
 127	// 0x04000014: BG1HOFS
 128	regGBA.append({
 129		{ tr("Horizontal offset"), 0, 9 },
 130	});
 131	// 0x04000016: BG1VOFS
 132	regGBA.append({
 133		{ tr("Vertical offset"), 0, 9 },
 134	});
 135	// 0x04000018: BG2HOFS
 136	regGBA.append({
 137		{ tr("Horizontal offset"), 0, 9 },
 138	});
 139	// 0x0400001A: BG2VOFS
 140	regGBA.append({
 141		{ tr("Vertical offset"), 0, 9 },
 142	});
 143	// 0x0400001C: BG3HOFS
 144	regGBA.append({
 145		{ tr("Horizontal offset"), 0, 9 },
 146	});
 147	// 0x0400001E: BG3VOFS
 148	regGBA.append({
 149		{ tr("Vertical offset"), 0, 9 },
 150	});
 151	// 0x04000020: BG2PA
 152	regGBA.append({
 153		{ tr("Fractional part"), 0, 8 },
 154		{ tr("Integer part"), 8, 8 },
 155	});
 156	// 0x04000022: BG2PB
 157	regGBA.append({
 158		{ tr("Fractional part"), 0, 8 },
 159		{ tr("Integer part"), 8, 8 },
 160	});
 161	// 0x04000024: BG2PC
 162	regGBA.append({
 163		{ tr("Fractional part"), 0, 8 },
 164		{ tr("Integer part"), 8, 8 },
 165	});
 166	// 0x04000026: BG2PD
 167	regGBA.append({
 168		{ tr("Fractional part"), 0, 8 },
 169		{ tr("Integer part"), 8, 8 },
 170	});
 171	// 0x04000028: BG2X_LO
 172	regGBA.append({
 173		{ tr("Fractional part"), 0, 8 },
 174		{ tr("Integer part (low)"), 8, 8 },
 175	});
 176	// 0x0400002A: BG2X_HI
 177	regGBA.append({
 178		{ tr("Integer part (high)"), 0, 12 },
 179	});
 180	// 0x0400002C: BG2Y_LO
 181	regGBA.append({
 182		{ tr("Fractional part"), 0, 8 },
 183		{ tr("Integer part (low)"), 8, 8 },
 184	});
 185	// 0x0400002E: BG2Y_HI
 186	regGBA.append({
 187		{ tr("Integer part (high)"), 0, 12 },
 188	});
 189	// 0x04000030: BG3PA
 190	regGBA.append({
 191		{ tr("Fractional part"), 0, 8 },
 192		{ tr("Integer part"), 8, 8 },
 193	});
 194	// 0x04000032: BG3PB
 195	regGBA.append({
 196		{ tr("Fractional part"), 0, 8 },
 197		{ tr("Integer part"), 8, 8 },
 198	});
 199	// 0x04000034: BG3PC
 200	regGBA.append({
 201		{ tr("Fractional part"), 0, 8 },
 202		{ tr("Integer part"), 8, 8 },
 203	});
 204	// 0x04000036: BG3PD
 205	regGBA.append({
 206		{ tr("Fractional part"), 0, 8 },
 207		{ tr("Integer part"), 8, 8 },
 208	});
 209	// 0x04000038: BG3X_LO
 210	regGBA.append({
 211		{ tr("Fractional part"), 0, 8 },
 212		{ tr("Integer part (low)"), 8, 8 },
 213	});
 214	// 0x0400003A: BG3X_HI
 215	regGBA.append({
 216		{ tr("Integer part (high)"), 0, 12 },
 217	});
 218	// 0x0400003C: BG3Y_LO
 219	regGBA.append({
 220		{ tr("Fractional part"), 0, 8 },
 221		{ tr("Integer part (low)"), 8, 8 },
 222	});
 223	// 0x0400003E: BG3Y_HI
 224	regGBA.append({
 225		{ tr("Integer part (high)"), 0, 12 },
 226	});
 227	// 0x04000040: WIN0H
 228	regGBA.append({
 229		{ tr("End x"), 0, 8 },
 230		{ tr("Start x"), 8, 8 },
 231	});
 232	// 0x04000042: WIN1H
 233	regGBA.append({
 234		{ tr("End x"), 0, 8 },
 235		{ tr("Start x"), 8, 8 },
 236	});
 237	// 0x04000044: WIN0V
 238	regGBA.append({
 239		{ tr("End y"), 0, 8 },
 240		{ tr("Start y"), 8, 8 },
 241	});
 242	// 0x04000046: WIN1V
 243	regGBA.append({
 244		{ tr("End y"), 0, 8 },
 245		{ tr("Start y"), 8, 8 },
 246	});
 247	// 0x04000048: WININ
 248	regGBA.append({
 249		{ tr("Window 0 enable BG 0"), 0 },
 250		{ tr("Window 0 enable BG 1"), 1 },
 251		{ tr("Window 0 enable BG 2"), 2 },
 252		{ tr("Window 0 enable BG 3"), 3 },
 253		{ tr("Window 0 enable OBJ"), 4 },
 254		{ tr("Window 0 enable blend"), 5 },
 255		{ tr("Window 1 enable BG 0"), 8 },
 256		{ tr("Window 1 enable BG 1"), 9 },
 257		{ tr("Window 1 enable BG 2"), 10 },
 258		{ tr("Window 1 enable BG 3"), 11 },
 259		{ tr("Window 1 enable OBJ"), 12 },
 260		{ tr("Window 1 enable blend"), 13 },
 261	});
 262	// 0x0400004A: WINOUT
 263	regGBA.append({
 264		{ tr("Outside window enable BG 0"), 0 },
 265		{ tr("Outside window enable BG 1"), 1 },
 266		{ tr("Outside window enable BG 2"), 2 },
 267		{ tr("Outside window enable BG 3"), 3 },
 268		{ tr("Outside window enable OBJ"), 4 },
 269		{ tr("Outside window enable blend"), 5 },
 270		{ tr("OBJ window enable BG 0"), 8 },
 271		{ tr("OBJ window enable BG 1"), 9 },
 272		{ tr("OBJ window enable BG 2"), 10 },
 273		{ tr("OBJ window enable BG 3"), 11 },
 274		{ tr("OBJ window enable OBJ"), 12 },
 275		{ tr("OBJ window enable blend"), 13 },
 276	});
 277	// 0x0400004C: MOSAIC
 278	regGBA.append({
 279		{ tr("Background mosaic size horizontal"), 0, 4 },
 280		{ tr("Background mosaic size vertical"), 4, 4 },
 281		{ tr("Object mosaic size horizontal"), 8, 4 },
 282		{ tr("Object mosaic size vertical"), 12, 4 },
 283	});
 284	// 0x0400004E: Unused
 285	regGBA.append(RegisterDescription());
 286	// 0x04000050: BLDCNT
 287	regGBA.append({
 288		{ tr("BG 0 target 1"), 0 },
 289		{ tr("BG 1 target 1"), 1 },
 290		{ tr("BG 2 target 1"), 2 },
 291		{ tr("BG 3 target 1"), 3 },
 292		{ tr("OBJ target 1"), 4 },
 293		{ tr("Backdrop target 1"), 5 },
 294		{ tr("Blend mode"), 6, 2, {
 295			tr("Disabled"),
 296			tr("Additive blending"),
 297			tr("Brighten"),
 298			tr("Darken"),
 299		} },
 300		{ tr("BG 0 target 2"), 8 },
 301		{ tr("BG 1 target 2"), 9 },
 302		{ tr("BG 2 target 2"), 10 },
 303		{ tr("BG 3 target 2"), 11 },
 304		{ tr("OBJ target 2"), 12 },
 305		{ tr("Backdrop target 2"), 13 },
 306	});
 307	// 0x04000052: BLDALPHA
 308	regGBA.append({
 309		{ tr("Blend A (target 1)"), 0, 5 },
 310		{ tr("Blend B (target 2)"), 8, 5 },
 311	});
 312	// 0x04000054: BLDY
 313	regGBA.append({
 314		{ tr("Blend Y"), 0, 5 },
 315	});
 316	// 0x04000056: Unused
 317	regGBA.append(RegisterDescription());
 318	// 0x04000058: Unused
 319	regGBA.append(RegisterDescription());
 320	// 0x0400005A: Unused
 321	regGBA.append(RegisterDescription());
 322	// 0x0400005C: Unused
 323	regGBA.append(RegisterDescription());
 324	// 0x0400005E: Unused
 325	regGBA.append(RegisterDescription());
 326	// 0x04000060: SOUND1CNT_LO
 327	regGBA.append({
 328		{ tr("Sweep shifts"), 0, 3 },
 329		{ tr("Sweep subtract"), 3 },
 330		{ tr("Sweep time (in 1/128s)"), 4, 3 },
 331	});
 332	// 0x04000062: SOUND1CNT_HI
 333	regGBA.append({
 334		{ tr("Sound length"), 0, 6 },
 335		{ tr("Duty cycle"),  6, 2 },
 336		{ tr("Envelope step time"), 8, 3 },
 337		{ tr("Envelope increase"), 11 },
 338		{ tr("Initial volume"), 12, 4 },
 339	});
 340	// 0x04000064: SOUND1CNT_X
 341	regGBA.append({
 342		{ tr("Sound frequency"), 0, 11 },
 343		{ tr("Timed"),  14 },
 344		{ tr("Reset"), 15 },
 345	});
 346	// 0x04000066: Unused
 347	regGBA.append(RegisterDescription());
 348	// 0x04000068: SOUND2CNT_LO
 349	regGBA.append({
 350		{ tr("Sound length"), 0, 6 },
 351		{ tr("Duty cycle"),  6, 2 },
 352		{ tr("Envelope step time"), 8, 3 },
 353		{ tr("Envelope increase"), 11 },
 354		{ tr("Initial volume"), 12, 4 },
 355	});
 356	// 0x0400006A: Unused
 357	regGBA.append(RegisterDescription());
 358	// 0x0400006C: SOUND2CNT_HI
 359	regGBA.append({
 360		{ tr("Sound frequency"), 0, 11 },
 361		{ tr("Timed"),  14 },
 362		{ tr("Reset"), 15 },
 363	});
 364	// 0x0400006E: Unused
 365	regGBA.append(RegisterDescription());
 366	// 0x04000070: SOUND3CNT_LO
 367	regGBA.append({
 368		{ tr("Double-size wave table"), 5 },
 369		{ tr("Active wave table"),  6 },
 370		{ tr("Enable channel 3"), 7 },
 371	});
 372	// 0x04000072: SOUND3CNT_HI
 373	regGBA.append({
 374		{ tr("Sound length"), 0, 8 },
 375		{ tr("Volume"),  13, 3, {
 376			tr("0%"),
 377			tr("100%"),
 378			tr("50%"),
 379			tr("25%"),
 380			tr("75%"),
 381			tr("75%"),
 382			tr("75%"),
 383			tr("75%")
 384		} },
 385	});
 386	// 0x04000074: SOUND3CNT_X
 387	regGBA.append({
 388		{ tr("Sound frequency"), 0, 11 },
 389		{ tr("Timed"),  14 },
 390		{ tr("Reset"), 15 },
 391	});
 392	// 0x04000076: Unused
 393	regGBA.append(RegisterDescription());
 394	// 0x04000078: SOUND4CNT_LO
 395	regGBA.append({
 396		{ tr("Sound length"), 0, 6 },
 397		{ tr("Envelope step time"), 8, 3 },
 398		{ tr("Envelope increase"), 11 },
 399		{ tr("Initial volume"), 12, 4 },
 400	});
 401	// 0x0400007A: Unused
 402	regGBA.append(RegisterDescription());
 403	// 0x0400007C: SOUND4CNT_HI
 404	regGBA.append({
 405		{ tr("Clock divider"), 0, 3 },
 406		{ tr("Register stages"), 3, 1, {
 407			tr("15"),
 408			tr("7"),
 409		} },
 410		{ tr("Shifter frequency"), 4, 4 },
 411		{ tr("Timed"),  14 },
 412		{ tr("Reset"), 15 },
 413	});
 414	// 0x0400007E: Unused
 415	regGBA.append(RegisterDescription());
 416	// 0x04000080: SOUNDCNT_LO
 417	regGBA.append({
 418		{ tr("PSG volume right"), 0, 3 },
 419		{ tr("PSG volume left"), 4, 3 },
 420		{ tr("Enable channel 1 right"), 8 },
 421		{ tr("Enable channel 2 right"), 9 },
 422		{ tr("Enable channel 3 right"), 10 },
 423		{ tr("Enable channel 4 right"), 11 },
 424		{ tr("Enable channel 1 left"), 12 },
 425		{ tr("Enable channel 2 left"), 13 },
 426		{ tr("Enable channel 3 left"), 14 },
 427		{ tr("Enable channel 4 left"), 15 },
 428	});
 429	// 0x04000082: SOUNDCNT_HI
 430	regGBA.append({
 431		{ tr("PSG master volume"), 0, 2, {
 432			tr("25%"),
 433			tr("50%"),
 434			tr("100%"),
 435			QString()
 436		} },
 437		{ tr("Loud channel A"), 2 },
 438		{ tr("Loud channel B"), 3 },
 439		{ tr("Enable channel A right"), 8 },
 440		{ tr("Enable channel A left"), 9 },
 441		{ tr("Channel A timer"), 10, 1, {
 442			tr("0"),
 443			tr("1"),
 444		} },
 445		{ tr("Channel A reset"), 11 },
 446		{ tr("Enable channel B right"), 12 },
 447		{ tr("Enable channel B left"), 13 },
 448		{ tr("Channel B timer"), 14, 1, {
 449			tr("0"),
 450			tr("1"),
 451		} },
 452		{ tr("Channel B reset"), 15 },
 453	});
 454	// 0x04000084: SOUNDCNT_LO
 455	regGBA.append({
 456		{ tr("Active channel 1"), 0, 1, true },
 457		{ tr("Active channel 2"), 1, 1, true },
 458		{ tr("Active channel 3"), 2, 1, true },
 459		{ tr("Active channel 4"), 3, 1, true },
 460		{ tr("Enable audio"), 7 },
 461	});
 462	// 0x04000086: Unused
 463	regGBA.append(RegisterDescription());
 464	// 0x04000088: SOUNDBIAS
 465	regGBA.append({
 466		{ tr("Bias"), 0, 10 },
 467		{ tr("Resolution"), 14, 2 },
 468	});
 469	// 0x0400008A: Unused
 470	regGBA.append(RegisterDescription());
 471	// 0x0400008C: Unused
 472	regGBA.append(RegisterDescription());
 473	// 0x0400008E: Unused
 474	regGBA.append(RegisterDescription());
 475	// 0x04000090: WAVE_RAM0_LO
 476	regGBA.append({
 477		{ tr("Sample"), 0, 4 },
 478		{ tr("Sample"), 4, 4 },
 479		{ tr("Sample"), 8, 4 },
 480		{ tr("Sample"), 12, 4 },
 481	});
 482	// 0x04000092: WAVE_RAM0_HI
 483	regGBA.append({
 484		{ tr("Sample"), 0, 4 },
 485		{ tr("Sample"), 4, 4 },
 486		{ tr("Sample"), 8, 4 },
 487		{ tr("Sample"), 12, 4 },
 488	});
 489	// 0x04000094: WAVE_RAM1_LO
 490	regGBA.append({
 491		{ tr("Sample"), 0, 4 },
 492		{ tr("Sample"), 4, 4 },
 493		{ tr("Sample"), 8, 4 },
 494		{ tr("Sample"), 12, 4 },
 495	});
 496	// 0x04000096: WAVE_RAM1_HI
 497	regGBA.append({
 498		{ tr("Sample"), 0, 4 },
 499		{ tr("Sample"), 4, 4 },
 500		{ tr("Sample"), 8, 4 },
 501		{ tr("Sample"), 12, 4 },
 502	});
 503	// 0x04000098: WAVE_RAM2_LO
 504	regGBA.append({
 505		{ tr("Sample"), 0, 4 },
 506		{ tr("Sample"), 4, 4 },
 507		{ tr("Sample"), 8, 4 },
 508		{ tr("Sample"), 12, 4 },
 509	});
 510	// 0x0400009A: WAVE_RAM2_HI
 511	regGBA.append({
 512		{ tr("Sample"), 0, 4 },
 513		{ tr("Sample"), 4, 4 },
 514		{ tr("Sample"), 8, 4 },
 515		{ tr("Sample"), 12, 4 },
 516	});
 517	// 0x0400009C: WAVE_RAM3_LO
 518	regGBA.append({
 519		{ tr("Sample"), 0, 4 },
 520		{ tr("Sample"), 4, 4 },
 521		{ tr("Sample"), 8, 4 },
 522		{ tr("Sample"), 12, 4 },
 523	});
 524	// 0x0400009E: WAVE_RAM0_HI
 525	regGBA.append({
 526		{ tr("Sample"), 0, 4 },
 527		{ tr("Sample"), 4, 4 },
 528		{ tr("Sample"), 8, 4 },
 529		{ tr("Sample"), 12, 4 },
 530	});
 531	// 0x040000A0: FIFO_A_LO
 532	regGBA.append({
 533		{ tr("Sample"), 0, 8 },
 534		{ tr("Sample"), 8, 8 },
 535	});
 536	// 0x040000A2: FIFO_A_HI
 537	regGBA.append({
 538		{ tr("Sample"), 0, 8 },
 539		{ tr("Sample"), 8, 8 },
 540	});
 541	// 0x040000A4: FIFO_B_LO
 542	regGBA.append({
 543		{ tr("Sample"), 0, 8 },
 544		{ tr("Sample"), 8, 8 },
 545	});
 546	// 0x040000A6: FIFO_B_HI
 547	regGBA.append({
 548		{ tr("Sample"), 0, 8 },
 549		{ tr("Sample"), 8, 8 },
 550	});
 551	// 0x040000A8: Unused
 552	regGBA.append(RegisterDescription());
 553	// 0x040000AA: Unused
 554	regGBA.append(RegisterDescription());
 555	// 0x040000AC: Unused
 556	regGBA.append(RegisterDescription());
 557	// 0x040000AE: Unused
 558	regGBA.append(RegisterDescription());
 559	// 0x040000B0: DMA0SAD_LO
 560	regGBA.append({
 561		{ tr("Address (low)"), 0, 16 },
 562	});
 563	// 0x040000B2: DMA0SAD_HI
 564	regGBA.append({
 565		{ tr("Address (high)"), 0, 16 },
 566	});
 567	// 0x040000B4: DMA0DAD_LO
 568	regGBA.append({
 569		{ tr("Address (low)"), 0, 16 },
 570	});
 571	// 0x040000B6: DMA0DAD_HI
 572	regGBA.append({
 573		{ tr("Address (high)"), 0, 16 },
 574	});
 575	// 0x040000B8: DMA0CNT_LO
 576	regGBA.append({
 577		{ tr("Word count"), 0, 16 },
 578	});
 579	// 0x040000BA: DMA0CNT_HI
 580	regGBA.append({
 581		{ tr("Destination offset"), 5, 2, {
 582			tr("Increment"),
 583			tr("Decrement"),
 584			tr("Fixed"),
 585			tr("Increment and reload"),
 586		} },
 587		{ tr("Source offset"), 7, 2, {
 588			tr("Increment"),
 589			tr("Decrement"),
 590			tr("Fixed"),
 591			QString(),
 592		} },
 593		{ tr("Repeat"), 9 },
 594		{ tr("32-bit"), 10 },
 595		{ tr("Start timing"), 12, 2, {
 596			tr("Immediate"),
 597			tr("VBlank"),
 598			tr("HBlank"),
 599			QString(),
 600		} },
 601		{ tr("IRQ"), 14 },
 602		{ tr("Enable"), 15 },
 603	});
 604	// 0x040000BC: DMA1SAD_LO
 605	regGBA.append({
 606		{ tr("Address (low)"), 0, 16 },
 607	});
 608	// 0x040000BE: DMA1SAD_HI
 609	regGBA.append({
 610		{ tr("Address (high)"), 0, 16 },
 611	});
 612	// 0x040000C0: DMA1DAD_LO
 613	regGBA.append({
 614		{ tr("Address (low)"), 0, 16 },
 615	});
 616	// 0x040000C2: DMA1DAD_HI
 617	regGBA.append({
 618		{ tr("Address (high)"), 0, 16 },
 619	});
 620	// 0x040000C4: DMA1CNT_LO
 621	regGBA.append({
 622		{ tr("Word count"), 0, 16 },
 623	});
 624	// 0x040000C6: DMA1CNT_HI
 625	regGBA.append({
 626		{ tr("Destination offset"), 5, 2, {
 627			tr("Increment"),
 628			tr("Decrement"),
 629			tr("Fixed"),
 630			tr("Increment and reload"),
 631		} },
 632		{ tr("Source offset"), 7, 2, {
 633			tr("Increment"),
 634			tr("Decrement"),
 635			tr("Fixed"),
 636			QString(),
 637		} },
 638		{ tr("Repeat"), 9 },
 639		{ tr("32-bit"), 10 },
 640		{ tr("Start timing"), 12, 2, {
 641			tr("Immediate"),
 642			tr("VBlank"),
 643			tr("HBlank"),
 644			tr("Audio FIFO"),
 645		} },
 646		{ tr("IRQ"), 14 },
 647		{ tr("Enable"), 15 },
 648	});
 649	// 0x040000C8: DMA2SAD_LO
 650	regGBA.append({
 651		{ tr("Address (low)"), 0, 16 },
 652	});
 653	// 0x040000CA: DMA2SAD_HI
 654	regGBA.append({
 655		{ tr("Address (high)"), 0, 16 },
 656	});
 657	// 0x040000CC: DMA2DAD_LO
 658	regGBA.append({
 659		{ tr("Address (low)"), 0, 16 },
 660	});
 661	// 0x040000CE: DMA2DAD_HI
 662	regGBA.append({
 663		{ tr("Address (high)"), 0, 16 },
 664	});
 665	// 0x040000D0: DMA2CNT_LO
 666	regGBA.append({
 667		{ tr("Word count"), 0, 16 },
 668	});
 669	// 0x040000D2: DMA2CNT_HI
 670	regGBA.append({
 671		{ tr("Destination offset"), 5, 2, {
 672			tr("Increment"),
 673			tr("Decrement"),
 674			tr("Fixed"),
 675			tr("Increment and reload"),
 676		} },
 677		{ tr("Source offset"), 7, 2, {
 678			tr("Increment"),
 679			tr("Decrement"),
 680			tr("Fixed"),
 681			QString(),
 682		} },
 683		{ tr("Repeat"), 9 },
 684		{ tr("32-bit"), 10 },
 685		{ tr("Start timing"), 12, 2, {
 686			tr("Immediate"),
 687			tr("VBlank"),
 688			tr("HBlank"),
 689			tr("Audio FIFO"),
 690		} },
 691		{ tr("IRQ"), 14 },
 692		{ tr("Enable"), 15 },
 693	});
 694	// 0x040000D4: DMA3SAD_LO
 695	regGBA.append({
 696		{ tr("Address (low)"), 0, 16 },
 697	});
 698	// 0x040000D6: DMA3SAD_HI
 699	regGBA.append({
 700		{ tr("Address (high)"), 0, 16 },
 701	});
 702	// 0x040000D8: DMA3DAD_LO
 703	regGBA.append({
 704		{ tr("Address (low)"), 0, 16 },
 705	});
 706	// 0x040000DA: DMA3DAD_HI
 707	regGBA.append({
 708		{ tr("Address (high)"), 0, 16 },
 709	});
 710	// 0x040000DC: DMA3CNT_LO
 711	regGBA.append({
 712		{ tr("Word count"), 0, 16 },
 713	});
 714	// 0x040000DE: DMA3CNT_HI
 715	regGBA.append({
 716		{ tr("Destination offset"), 5, 2, {
 717			tr("Increment"),
 718			tr("Decrement"),
 719			tr("Fixed"),
 720			tr("Increment and reload"),
 721		} },
 722		{ tr("Source offset"), 7, 2, {
 723			tr("Increment"),
 724			tr("Decrement"),
 725			tr("Fixed"),
 726			tr("Video Capture"),
 727		} },
 728		{ tr("DRQ"), 8 },
 729		{ tr("Repeat"), 9 },
 730		{ tr("32-bit"), 10 },
 731		{ tr("Start timing"), 12, 2, {
 732			tr("Immediate"),
 733			tr("VBlank"),
 734			tr("HBlank"),
 735			tr("Audio FIFO"),
 736		} },
 737		{ tr("IRQ"), 14 },
 738		{ tr("Enable"), 15 },
 739	});
 740	// 0x040000E0: Unused
 741	regGBA.append(RegisterDescription());
 742	// 0x040000E2: Unused
 743	regGBA.append(RegisterDescription());
 744	// 0x040000E4: Unused
 745	regGBA.append(RegisterDescription());
 746	// 0x040000E6: Unused
 747	regGBA.append(RegisterDescription());
 748	// 0x040000E8: Unused
 749	regGBA.append(RegisterDescription());
 750	// 0x040000EA: Unused
 751	regGBA.append(RegisterDescription());
 752	// 0x040000EC: Unused
 753	regGBA.append(RegisterDescription());
 754	// 0x040000EE: Unused
 755	regGBA.append(RegisterDescription());
 756	// 0x040000F0: Unused
 757	regGBA.append(RegisterDescription());
 758	// 0x040000F2: Unused
 759	regGBA.append(RegisterDescription());
 760	// 0x040000F4: Unused
 761	regGBA.append(RegisterDescription());
 762	// 0x040000F6: Unused
 763	regGBA.append(RegisterDescription());
 764	// 0x040000F8: Unused
 765	regGBA.append(RegisterDescription());
 766	// 0x040000FA: Unused
 767	regGBA.append(RegisterDescription());
 768	// 0x040000FC: Unused
 769	regGBA.append(RegisterDescription());
 770	// 0x040000FE: Unused
 771	regGBA.append(RegisterDescription());
 772	// 0x04000100: TM0CNT_LO
 773	regGBA.append({
 774		{ tr("Value"), 0, 16 },
 775	});
 776	// 0x04000102: TM0CNT_HI
 777	regGBA.append({
 778		{ tr("Scale"), 0, 2, {
 779			tr("1"),
 780			tr("1/64"),
 781			tr("1/256"),
 782			tr("1/1024"),
 783		} },
 784		{ tr("IRQ"), 6 },
 785		{ tr("Enable"), 7 },
 786	});
 787	// 0x04000104: TM1CNT_LO
 788	regGBA.append({
 789		{ tr("Value"), 0, 16 },
 790	});
 791	// 0x04000106: TM1CNT_HI
 792	regGBA.append({
 793		{ tr("Scale"), 0, 2, {
 794			tr("1"),
 795			tr("1/64"),
 796			tr("1/256"),
 797			tr("1/1024"),
 798		} },
 799		{ tr("Cascade"), 2 },
 800		{ tr("IRQ"), 6 },
 801		{ tr("Enable"), 7 },
 802	});
 803	// 0x04000108: TM2CNT_LO
 804	regGBA.append({
 805		{ tr("Value"), 0, 16 },
 806	});
 807	// 0x0400010A: TM2CNT_HI
 808	regGBA.append({
 809		{ tr("Scale"), 0, 2, {
 810			tr("1"),
 811			tr("1/64"),
 812			tr("1/256"),
 813			tr("1/1024"),
 814		} },
 815		{ tr("Cascade"), 2 },
 816		{ tr("IRQ"), 6 },
 817		{ tr("Enable"), 7 },
 818	});
 819	// 0x0400010C: TM3CNT_LO
 820	regGBA.append({
 821		{ tr("Value"), 0, 16 },
 822	});
 823	// 0x0400010E: TM3CNT_HI
 824	regGBA.append({
 825		{ tr("Scale"), 0, 2, {
 826			tr("1"),
 827			tr("1/64"),
 828			tr("1/256"),
 829			tr("1/1024"),
 830		} },
 831		{ tr("Cascade"), 2 },
 832		{ tr("IRQ"), 6 },
 833		{ tr("Enable"), 7 },
 834	});
 835	// 0x04000110: Unused
 836	regGBA.append(RegisterDescription());
 837	// 0x04000112: Unused
 838	regGBA.append(RegisterDescription());
 839	// 0x04000114: Unused
 840	regGBA.append(RegisterDescription());
 841	// 0x04000116: Unused
 842	regGBA.append(RegisterDescription());
 843	// 0x04000118: Unused
 844	regGBA.append(RegisterDescription());
 845	// 0x0400011A: Unused
 846	regGBA.append(RegisterDescription());
 847	// 0x0400011C: Unused
 848	regGBA.append(RegisterDescription());
 849	// 0x0400011E: Unused
 850	regGBA.append(RegisterDescription());
 851	// 0x04000120: SIOMULTI0
 852	regGBA.append(RegisterDescription());
 853	// 0x04000122: SIOMULTI1
 854	regGBA.append(RegisterDescription());
 855	// 0x04000124: SIOMULTI2
 856	regGBA.append(RegisterDescription());
 857	// 0x04000126: SIOMULTI3
 858	regGBA.append(RegisterDescription());
 859	// 0x04000128: SIOCNT
 860	regGBA.append(RegisterDescription());
 861	// 0x0400012A: SIOMLT_SEND
 862	regGBA.append(RegisterDescription());
 863	// 0x0400012C: Unused
 864	regGBA.append(RegisterDescription());
 865	// 0x0400012E: Unused
 866	regGBA.append(RegisterDescription());
 867	// 0x04000130: KEYINPUT
 868	regGBA.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	});
 880	// 0x04000132: KEYCNT
 881	regGBA.append({
 882		{ tr("A"), 0 },
 883		{ tr("B"), 1 },
 884		{ tr("Select"), 2 },
 885		{ tr("Start"), 3 },
 886		{ tr("Right"), 4 },
 887		{ tr("Left"), 5 },
 888		{ tr("Up"), 6 },
 889		{ tr("Down"), 7 },
 890		{ tr("R"), 8 },
 891		{ tr("L"), 9 },
 892		{ tr("IRQ"), 14 },
 893		{ tr("Condition"), 15 },
 894	});
 895	// 0x04000134: RCNT
 896	regGBA.append({
 897		{ tr("SC"), 0 },
 898		{ tr("SD"), 1 },
 899		{ tr("SI"), 2 },
 900		{ tr("SO"), 3 },
 901	});
 902	// 0x04000136: Unused
 903	regGBA.append(RegisterDescription());
 904	// 0x04000138: SIOCNT
 905	regGBA.append(RegisterDescription());
 906	// 0x0400013A: Unused
 907	regGBA.append(RegisterDescription());
 908	// 0x0400013C: Unused
 909	regGBA.append(RegisterDescription());
 910	// 0x0400013E: Unused
 911	regGBA.append(RegisterDescription());
 912	// 0x04000140: JOYCNT
 913	regGBA.append(RegisterDescription());
 914	// 0x04000142: Unused
 915	regGBA.append(RegisterDescription());
 916	// 0x04000144: Unused
 917	regGBA.append(RegisterDescription());
 918	// 0x04000146: Unused
 919	regGBA.append(RegisterDescription());
 920	// 0x04000148: Unused
 921	regGBA.append(RegisterDescription());
 922	// 0x0400014A: Unused
 923	regGBA.append(RegisterDescription());
 924	// 0x0400014C: Unused
 925	regGBA.append(RegisterDescription());
 926	// 0x0400014E: Unused
 927	regGBA.append(RegisterDescription());
 928	// 0x04000150: JOY_RECV_LO
 929	regGBA.append(RegisterDescription());
 930	// 0x04000152: JOY_RECV_HI
 931	regGBA.append(RegisterDescription());
 932	// 0x04000154: JOY_TRANS_LO
 933	regGBA.append(RegisterDescription());
 934	// 0x04000156: JOY_TRANS_HI
 935	regGBA.append(RegisterDescription());
 936	// 0x04000158: JOYSTAT
 937	regGBA.append(RegisterDescription());
 938	for (int i = 0x15A; i < 0x200; i += 2) {
 939		// Unused
 940		regGBA.append(RegisterDescription());
 941	}
 942	// 0x04000200: IE
 943	regGBA.append({
 944		{ tr("VBlank"), 0 },
 945		{ tr("HBlank"), 1 },
 946		{ tr("VCounter"), 2 },
 947		{ tr("Timer 0"), 3 },
 948		{ tr("Timer 1"), 4 },
 949		{ tr("Timer 2"), 5 },
 950		{ tr("Timer 3"), 6 },
 951		{ tr("SIO"), 7 },
 952		{ tr("DMA 0"), 8 },
 953		{ tr("DMA 1"), 9 },
 954		{ tr("DMA 2"), 10 },
 955		{ tr("DMA 3"), 11 },
 956		{ tr("Keypad"), 12 },
 957		{ tr("Gamepak"), 13 },
 958	});
 959	// 0x04000202: IF
 960	regGBA.append({
 961		{ tr("VBlank"), 0 },
 962		{ tr("HBlank"), 1 },
 963		{ tr("VCounter"), 2 },
 964		{ tr("Timer 0"), 3 },
 965		{ tr("Timer 1"), 4 },
 966		{ tr("Timer 2"), 5 },
 967		{ tr("Timer 3"), 6 },
 968		{ tr("SIO"), 7 },
 969		{ tr("DMA 0"), 8 },
 970		{ tr("DMA 1"), 9 },
 971		{ tr("DMA 2"), 10 },
 972		{ tr("DMA 3"), 11 },
 973		{ tr("Keypad"), 12 },
 974		{ tr("Gamepak"), 13 },
 975	});
 976	// 0x04000204: WAITCNT
 977	regGBA.append({
 978		{ tr("SRAM wait"), 0, 2, {
 979			tr("4"),
 980			tr("3"),
 981			tr("2"),
 982			tr("8"),
 983		} },
 984		{ tr("Cart 0 non-sequential"), 2, 2, {
 985			tr("4"),
 986			tr("3"),
 987			tr("2"),
 988			tr("8"),
 989		} },
 990		{ tr("Cart 0 sequential"), 4, 1, {
 991			tr("2"),
 992			tr("1"),
 993		} },
 994		{ tr("Cart 1 non-sequential"), 5, 2, {
 995			tr("4"),
 996			tr("3"),
 997			tr("2"),
 998			tr("8"),
 999		} },
1000		{ tr("Cart 1 sequential"), 7, 1, {
1001			tr("4"),
1002			tr("1"),
1003		} },
1004		{ tr("Cart 2 non-sequential"), 8, 2, {
1005			tr("4"),
1006			tr("3"),
1007			tr("2"),
1008			tr("8"),
1009		} },
1010		{ tr("Cart 2 sequential"), 10, 1, {
1011			tr("8"),
1012			tr("1"),
1013		} },
1014		{ tr("PHI terminal"), 11, 2, {
1015			tr("Disable"),
1016			tr("4.19MHz"),
1017			tr("8.38MHz"),
1018			tr("16.78MHz"),
1019		} },
1020		{ tr("Gamepak prefetch"), 14 },
1021	});
1022	// 0x04000206: Unused
1023	regGBA.append(RegisterDescription());
1024	// 0x04000208: IME
1025	regGBA.append({
1026		{ tr("Enable IRQs"), 0 },
1027	});
1028	s_registers[mPLATFORM_GBA] = regGBA;
1029#endif
1030#ifdef M_CORE_GB
1031	QList<IOViewer::RegisterDescription> regGB;
1032	// 0xFF00: JOYP
1033	regGB.append({
1034		{ tr("Right/A"), 0, 1, true },
1035		{ tr("Left/B"), 1, 1, true },
1036		{ tr("Up/Select"), 2, 1, true },
1037		{ tr("Down/Start"), 3, 1, true },
1038		{ tr("Active D-pad"), 4 },
1039		{ tr("Active face buttons"), 5 },
1040	});
1041	// 0xFF01: SB
1042	regGB.append({
1043		{ tr("Value"), 0, 8 },
1044	});
1045	// 0xFF02: SC
1046	regGB.append({
1047		{ tr("Internal clock"), 0 },
1048		{ tr("32× clocking (CGB only)"), 1 },
1049		{ tr("Transfer active"), 7 },
1050	});
1051	// 0xFF03: Unused
1052	regGB.append(RegisterDescription());
1053	// 0xFF04: DIV
1054	regGB.append({
1055		{ tr("Value"), 0, 8 },
1056	});
1057	// 0xFF05: TIMA
1058	regGB.append({
1059		{ tr("Value"), 0, 8 },
1060	});
1061	// 0xFF06: TMA
1062	regGB.append({
1063		{ tr("Value"), 0, 8 },
1064	});
1065	// 0xFF07: TAC
1066	regGB.append({
1067		{ tr("Divider"), 0, 2, {
1068			tr("1/1024"),
1069			tr("1/16"),
1070			tr("1/64"),
1071			tr("1/256"),
1072		} },
1073		{ tr("Enable"), 2 },
1074	});
1075	// 0xFF08: Unused
1076	regGB.append(RegisterDescription());
1077	// 0xFF09: Unused
1078	regGB.append(RegisterDescription());
1079	// 0xFF0A: Unused
1080	regGB.append(RegisterDescription());
1081	// 0xFF0B: Unused
1082	regGB.append(RegisterDescription());
1083	// 0xFF0C: Unused
1084	regGB.append(RegisterDescription());
1085	// 0xFF0D: Unused
1086	regGB.append(RegisterDescription());
1087	// 0xFF0E: Unused
1088	regGB.append(RegisterDescription());
1089	// 0xFF0F: IF
1090	regGB.append({
1091		{ tr("VBlank"), 0 },
1092		{ tr("LCD STAT"), 1 },
1093		{ tr("Timer"), 2 },
1094		{ tr("Serial"), 3 },
1095		{ tr("Joypad"), 4 },
1096	});
1097	// 0xFF10: NR10
1098	regGB.append({
1099		{ tr("Sweep shifts"), 0, 3 },
1100		{ tr("Sweep subtract"), 3 },
1101		{ tr("Sweep time (in 1/128s)"), 4, 3 },
1102	});
1103	// 0xFF11: NR11
1104	regGB.append({
1105		{ tr("Sound length"), 0, 6 },
1106		{ tr("Duty cycle"),  6, 2 },
1107	});
1108	// 0xFF12: NR12
1109	regGB.append({
1110		{ tr("Envelope step time"), 0, 3 },
1111		{ tr("Envelope increase"), 3 },
1112		{ tr("Initial volume"), 4, 4 },
1113	});
1114	// 0xFF13: NR13
1115	regGB.append({
1116		{ tr("Sound frequency (low)"), 0, 8 },
1117	});
1118	// 0xFF14: NR14
1119	regGB.append({
1120		{ tr("Sound frequency (high)"), 0, 3 },
1121		{ tr("Timed"), 6 },
1122		{ tr("Reset"), 7 },
1123	});
1124	// 0xFF15: Unused (NR20)
1125	regGB.append(RegisterDescription());
1126	// 0xFF16: NR21
1127	regGB.append({
1128		{ tr("Sound length"), 0, 6 },
1129		{ tr("Duty cycle"),  6, 2 },
1130	});
1131	// 0xFF17: NR22
1132	regGB.append({
1133		{ tr("Envelope step time"), 0, 3 },
1134		{ tr("Envelope increase"), 3 },
1135		{ tr("Initial volume"), 4, 4 },
1136	});
1137	// 0xFF18: NR23
1138	regGB.append({
1139		{ tr("Sound frequency (low)"), 0, 8 },
1140	});
1141	// 0xFF19: NR24
1142	regGB.append({
1143		{ tr("Sound frequency (high)"), 0, 3 },
1144		{ tr("Timed"), 6 },
1145		{ tr("Reset"), 7 },
1146	});
1147	// 0xFF1A: NR30
1148	regGB.append({
1149		{ tr("Enable channel 3"), 7 },
1150	});
1151	// 0xFF1B: NR31
1152	regGB.append({
1153		{ tr("Sound length"), 0, 8 },
1154	});
1155	// 0xFF1C: NR32
1156	regGB.append({
1157		{ tr("Volume"),  5, 2, {
1158			tr("0%"),
1159			tr("100%"),
1160			tr("50%"),
1161			tr("25%"),
1162		} },
1163	});
1164	// 0xFF1D: NR33
1165	regGB.append({
1166		{ tr("Sound frequency (low)"), 0, 8 },
1167	});
1168	// 0xFF1E: NR34
1169	regGB.append({
1170		{ tr("Sound frequency (high)"), 0, 3 },
1171		{ tr("Timed"), 6 },
1172		{ tr("Reset"), 7 },
1173	});
1174	// 0xFF1F: Unusued (NR40)
1175	regGB.append(RegisterDescription());
1176	// 0xFF20: NR41
1177	regGB.append({
1178		{ tr("Sound length"), 0, 6 },
1179	});
1180	// 0xFF21: NR42
1181	regGB.append({
1182		{ tr("Envelope step time"), 0, 3 },
1183		{ tr("Envelope increase"), 3 },
1184		{ tr("Initial volume"), 4, 4 },
1185	});
1186	// 0xFF22: NR43
1187	regGB.append({
1188		{ tr("Clock divider"), 0, 3 },
1189		{ tr("Register stages"), 3, 1, {
1190			tr("15"),
1191			tr("7"),
1192		} },
1193		{ tr("Shifter frequency"), 4, 4 },
1194	});
1195	// 0xFF23: NR44
1196	regGB.append({
1197		{ tr("Timed"), 6 },
1198		{ tr("Reset"), 7 },
1199	});
1200	// 0xFF24: NR50
1201	regGB.append({
1202		{ tr("Volume right"), 0, 3 },
1203		{ tr("Output right"), 3 },
1204		{ tr("Volume left"), 4, 3 },
1205		{ tr("Output left"), 7 },
1206	});
1207	// 0xFF25: NR51
1208	regGB.append({
1209		{ tr("Enable channel 1 right"), 0 },
1210		{ tr("Enable channel 2 right"), 1 },
1211		{ tr("Enable channel 3 right"), 2 },
1212		{ tr("Enable channel 4 right"), 3 },
1213		{ tr("Enable channel 1 left"), 4 },
1214		{ tr("Enable channel 2 left"), 5 },
1215		{ tr("Enable channel 3 left"), 6 },
1216		{ tr("Enable channel 4 left"), 7 },
1217	});
1218	// 0xFF26: NR52
1219	regGB.append({
1220		{ tr("Active channel 1"), 0, 1, true },
1221		{ tr("Active channel 2"), 1, 1, true },
1222		{ tr("Active channel 3"), 2, 1, true },
1223		{ tr("Active channel 4"), 3, 1, true },
1224		{ tr("Enable audio"), 7 },
1225	});
1226	// 0xFF27: Unused
1227	regGB.append(RegisterDescription());
1228	// 0xFF28: Unused
1229	regGB.append(RegisterDescription());
1230	// 0xFF29: Unused
1231	regGB.append(RegisterDescription());
1232	// 0xFF2A: Unused
1233	regGB.append(RegisterDescription());
1234	// 0xFF2B: Unused
1235	regGB.append(RegisterDescription());
1236	// 0xFF2C: Unused
1237	regGB.append(RegisterDescription());
1238	// 0xFF2D: Unused
1239	regGB.append(RegisterDescription());
1240	// 0xFF2E: Unused
1241	regGB.append(RegisterDescription());
1242	// 0xFF2F: Unused
1243	regGB.append(RegisterDescription());
1244	// 0xFF30: WAVE_0
1245	regGB.append({
1246		{ tr("Sample"), 0, 4 },
1247		{ tr("Sample"), 4, 4 },
1248	});
1249	// 0xFF31: WAVE_1
1250	regGB.append({
1251		{ tr("Sample"), 0, 4 },
1252		{ tr("Sample"), 4, 4 },
1253	});
1254	// 0xFF32: WAVE_2
1255	regGB.append({
1256		{ tr("Sample"), 0, 4 },
1257		{ tr("Sample"), 4, 4 },
1258	});
1259	// 0xFF33: WAVE_3
1260	regGB.append({
1261		{ tr("Sample"), 0, 4 },
1262		{ tr("Sample"), 4, 4 },
1263	});
1264	// 0xFF34: WAVE_4
1265	regGB.append({
1266		{ tr("Sample"), 0, 4 },
1267		{ tr("Sample"), 4, 4 },
1268	});
1269	// 0xFF35: WAVE_5
1270	regGB.append({
1271		{ tr("Sample"), 0, 4 },
1272		{ tr("Sample"), 4, 4 },
1273	});
1274	// 0xFF36: WAVE_6
1275	regGB.append({
1276		{ tr("Sample"), 0, 4 },
1277		{ tr("Sample"), 4, 4 },
1278	});
1279	// 0xFF37: WAVE_7
1280	regGB.append({
1281		{ tr("Sample"), 0, 4 },
1282		{ tr("Sample"), 4, 4 },
1283	});
1284	// 0xFF38: WAVE_8
1285	regGB.append({
1286		{ tr("Sample"), 0, 4 },
1287		{ tr("Sample"), 4, 4 },
1288	});
1289	// 0xFF39: WAVE_9
1290	regGB.append({
1291		{ tr("Sample"), 0, 4 },
1292		{ tr("Sample"), 4, 4 },
1293	});
1294	// 0xFF3A: WAVE_A
1295	regGB.append({
1296		{ tr("Sample"), 0, 4 },
1297		{ tr("Sample"), 4, 4 },
1298	});
1299	// 0xFF3B: WAVE_B
1300	regGB.append({
1301		{ tr("Sample"), 0, 4 },
1302		{ tr("Sample"), 4, 4 },
1303	});
1304	// 0xFF3C: WAVE_C
1305	regGB.append({
1306		{ tr("Sample"), 0, 4 },
1307		{ tr("Sample"), 4, 4 },
1308	});
1309	// 0xFF3D: WAVE_D
1310	regGB.append({
1311		{ tr("Sample"), 0, 4 },
1312		{ tr("Sample"), 4, 4 },
1313	});
1314	// 0xFF3E: WAVE_E
1315	regGB.append({
1316		{ tr("Sample"), 0, 4 },
1317		{ tr("Sample"), 4, 4 },
1318	});
1319	// 0xFF3F: WAVE_F
1320	regGB.append({
1321		{ tr("Sample"), 0, 4 },
1322		{ tr("Sample"), 4, 4 },
1323	});
1324	// 0xFF40: LCDC
1325	regGB.append({
1326		{ tr("Background enable/priority"), 1 },
1327		{ tr("Enable sprites"), 1 },
1328		{ tr("Double-height sprites"), 2 },
1329		{ tr("Background tile map"), 3, 1, {
1330			tr("0x9800 – 0x9BFF"),
1331			tr("0x9C00 – 0x9FFF"),
1332		} },
1333		{ tr("Background tile data"), 4, 1, {
1334			tr("0x8800 – 0x87FF"),
1335			tr("0x8000 – 0x8FFF"),
1336		} },
1337		{ tr("Enable window"), 5 },
1338		{ tr("Window tile map"), 6, 1, {
1339			tr("0x9800 – 0x9BFF"),
1340			tr("0x9C00 – 0x9FFF"),
1341		} },
1342		{ tr("Enable LCD"), 7 },
1343	});
1344	// 0xFF41: STAT
1345	regGB.append({
1346		{ tr("Mode"), 0, 2, {
1347			tr("0: HBlank"),
1348			tr("1: VBlank"),
1349			tr("2: OAM scan"),
1350			tr("3: HDraw"),
1351		}, true },
1352		{ tr("In LYC"), 2, 1, true },
1353		{ tr("Enable HBlank (mode 0) IRQ"), 3 },
1354		{ tr("Enable VBlank (mode 1) IRQ"), 4 },
1355		{ tr("Enable OAM (mode 2) IRQ"), 4 },
1356		{ tr("Enable LYC IRQ"), 4 },
1357	});
1358	// 0xFF42: SCY
1359	regGB.append({
1360		{ tr("Vertical offset"), 0, 8 },
1361	});
1362	// 0xFF43: SCX
1363	regGB.append({
1364		{ tr("Horizontal offset"), 0, 8 },
1365	});
1366	// 0xFF44: LY
1367	regGB.append({
1368		{ tr("Current Y coordinate"), 0, 8, true },
1369	});
1370	// 0xFF45: LYC
1371	regGB.append({
1372		{ tr("Comparison Y coordinate"), 0, 8 },
1373	});
1374	// 0xFF46: DMA
1375	regGB.append({
1376		{ tr("Start upper byte"), 0, 8 },
1377	});
1378	// 0xFF47: BGP
1379	regGB.append({
1380		{ tr("Color 0 shade"), 0, 2 },
1381		{ tr("Color 1 shade"), 2, 2 },
1382		{ tr("Color 2 shade"), 4, 2 },
1383		{ tr("Color 3 shade"), 6, 2 },
1384	});
1385	// 0xFF48: OBP0
1386	regGB.append({
1387		{ tr("Color 0 shade"), 0, 2 },
1388		{ tr("Color 1 shade"), 2, 2 },
1389		{ tr("Color 2 shade"), 4, 2 },
1390		{ tr("Color 3 shade"), 6, 2 },
1391	});
1392	// 0xFF49: OBP1
1393	regGB.append({
1394		{ tr("Color 0 shade"), 0, 2 },
1395		{ tr("Color 1 shade"), 2, 2 },
1396		{ tr("Color 2 shade"), 4, 2 },
1397		{ tr("Color 3 shade"), 6, 2 },
1398	});
1399	// 0xFF4A: WY
1400	regGB.append({
1401		{ tr("Vertical offset"), 0, 8 },
1402	});
1403	// 0xFF4B: WX
1404	regGB.append({
1405		{ tr("Horizontal offset"), 0, 8 },
1406	});
1407	// 0xFF4C: KEY0
1408	regGB.append(RegisterDescription());
1409	// 0xFF4D: KEY1
1410	regGB.append({
1411		{ tr("Prepare to switch speed"), 0 },
1412		{ tr("Double speed"), 7, 1, true },
1413	});
1414	// 0xFF4E: Unknown/unused
1415	regGB.append(RegisterDescription());
1416	// 0xFF4F: VBK
1417	regGB.append({
1418		{ tr("VRAM bank"), 0 },
1419	});
1420	// 0xFF50: BANK
1421	regGB.append(RegisterDescription());
1422	// 0xFF51: HDMA1
1423	regGB.append({
1424		{ tr("Source (high)"), 0, 8 },
1425	});
1426	// 0xFF52: HDMA2
1427	regGB.append({
1428		{ tr("Source (low)"), 0, 8 },
1429	});
1430	// 0xFF53: HDMA3
1431	regGB.append({
1432		{ tr("Destination (high)"), 0, 8 },
1433	});
1434	// 0xFF54: HDMA4
1435	regGB.append({
1436		{ tr("Destination (low)"), 0, 8 },
1437	});
1438	// 0xFF55: HDMA5
1439	regGB.append({
1440		{ tr("Length"), 0, 7 },
1441		{ tr("Timing"), 7, 1, {
1442			tr("Immediate"),
1443			tr("HBlank"),
1444		} },
1445	});
1446	// 0xFF56: RP
1447	regGB.append({
1448		{ tr("Write bit"), 0 },
1449		{ tr("Read bit"), 1, 1, true },
1450		{ tr("Enable"), 6, 2, {
1451			tr("Disable"),
1452			tr("Unknown"),
1453			tr("Unknown"),
1454			tr("Enable"),
1455		} },
1456	});
1457	// 0xFF57: Unknown/unused
1458	regGB.append(RegisterDescription());
1459	// 0xFF58: Unknown/unused
1460	regGB.append(RegisterDescription());
1461	// 0xFF59: Unknown/unused
1462	regGB.append(RegisterDescription());
1463	// 0xFF5A: Unknown/unused
1464	regGB.append(RegisterDescription());
1465	// 0xFF5B: Unknown/unused
1466	regGB.append(RegisterDescription());
1467	// 0xFF5C: Unknown/unused
1468	regGB.append(RegisterDescription());
1469	// 0xFF5D: Unknown/unused
1470	regGB.append(RegisterDescription());
1471	// 0xFF5E: Unknown/unused
1472	regGB.append(RegisterDescription());
1473	// 0xFF5F: Unknown/unused
1474	regGB.append(RegisterDescription());
1475	// 0xFF60: Unknown/unused
1476	regGB.append(RegisterDescription());
1477	// 0xFF61: Unknown/unused
1478	regGB.append(RegisterDescription());
1479	// 0xFF62: Unknown/unused
1480	regGB.append(RegisterDescription());
1481	// 0xFF63: Unknown/unused
1482	regGB.append(RegisterDescription());
1483	// 0xFF64: Unknown/unused
1484	regGB.append(RegisterDescription());
1485	// 0xFF65: Unknown/unused
1486	regGB.append(RegisterDescription());
1487	// 0xFF66: Unknown/unused
1488	regGB.append(RegisterDescription());
1489	// 0xFF67: Unknown/unused
1490	regGB.append(RegisterDescription());
1491	// 0xFF68: BCPS
1492	regGB.append({
1493		{ tr("Current index"), 0, 6 },
1494		{ tr("Auto-increment"), 7 },
1495	});
1496	// 0xFF69: BCPD
1497	regGB.append({
1498		{ tr("Red"), 0, 5 },
1499		{ tr("Green (low)"), 5, 3 },
1500		{ tr("Green (high)"), 0, 2 },
1501		{ tr("Blue"), 2, 5 },
1502	});
1503	// 0xFF6A: OCPS
1504	regGB.append({
1505		{ tr("Current index"), 0, 6 },
1506		{ tr("Auto-increment"), 7 },
1507	});
1508	// 0xFF6B: OCPD
1509	regGB.append({
1510		{ tr("Red"), 0, 5 },
1511		{ tr("Green (low)"), 5, 3 },
1512		{ tr("Green (high)"), 0, 2 },
1513		{ tr("Blue"), 2, 5 },
1514	});
1515	// 0xFF6C: OPRI
1516	regGB.append({
1517		{ tr("Sprite ordering"), 0, 1, {
1518			tr("OAM order"),
1519			tr("x coordinate sorting"),
1520		} },
1521	});
1522	// 0xFF6D: Unknown/unused
1523	regGB.append(RegisterDescription());
1524	// 0xFF6E: Unknown/unused
1525	regGB.append(RegisterDescription());
1526	// 0xFF6F: Unknown/unused
1527	regGB.append(RegisterDescription());
1528	// 0xFF70: SVBK
1529	regGB.append({
1530		{ tr("WRAM bank"), 0, 3 },
1531	});
1532	// 0xFF71: Unknown/unused
1533	regGB.append(RegisterDescription());
1534	// 0xFF72: Unknown
1535	regGB.append(RegisterDescription());
1536	// 0xFF73: Unknown
1537	regGB.append(RegisterDescription());
1538	// 0xFF74: Unknown
1539	regGB.append(RegisterDescription());
1540	// 0xFF75: Unknown
1541	regGB.append(RegisterDescription());
1542	// 0xFF76: PCM12
1543	regGB.append(RegisterDescription());
1544	// 0xFF77: PCM34
1545	regGB.append(RegisterDescription());
1546	for (int i = 0x78; i < 0xFF; ++i) {
1547		// Unused
1548		regGB.append(RegisterDescription());
1549	}
1550	// 0xFFFF: IE
1551	regGB.append({
1552		{ tr("VBlank"), 0 },
1553		{ tr("LCD STAT"), 1 },
1554		{ tr("Timer"), 2 },
1555		{ tr("Serial"), 3 },
1556		{ tr("Joypad"), 4 },
1557	});
1558	s_registers[mPLATFORM_GB] = regGB;
1559#endif
1560	return s_registers[platform];
1561}
1562
1563IOViewer::IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent)
1564	: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
1565	, m_controller(controller)
1566{
1567	m_ui.setupUi(this);
1568	const char* const* regs;
1569	unsigned maxRegs;
1570	switch (controller->platform()) {
1571#ifdef M_CORE_GB
1572	case mPLATFORM_GB:
1573		regs = GBIORegisterNames;
1574		maxRegs = GB_REG_MAX;
1575		m_base = GB_BASE_IO;
1576		m_width = 0;
1577		break;
1578#endif
1579#ifdef M_CORE_GBA
1580	case mPLATFORM_GBA:
1581		regs = GBAIORegisterNames;
1582		maxRegs = REG_MAX >> 1;
1583		m_base = BASE_IO;
1584		m_width = 1;
1585		break;
1586#endif
1587	}
1588
1589	for (unsigned i = 0; i < maxRegs; ++i) {
1590		const char* reg = regs[i];
1591		if (!reg) {
1592			continue;
1593		}
1594		m_ui.regSelect->addItem("0x" + QString("%1: %2").arg((i << m_width) + m_base, 4, 16, QChar('0')).toUpper().arg(reg), i << m_width);
1595	}
1596
1597	const QFont font = GBAApp::app()->monospaceFont();
1598	m_ui.regValue->setFont(font);
1599
1600	connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &IOViewer::buttonPressed);
1601	connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
1602	connect(m_ui.regSelect, &QComboBox::currentTextChanged,
1603	        this, static_cast<void (IOViewer::*)()>(&IOViewer::selectRegister));
1604
1605	m_b[0] = m_ui.b0;
1606	m_b[1] = m_ui.b1;
1607	m_b[2] = m_ui.b2;
1608	m_b[3] = m_ui.b3;
1609	m_b[4] = m_ui.b4;
1610	m_b[5] = m_ui.b5;
1611	m_b[6] = m_ui.b6;
1612	m_b[7] = m_ui.b7;
1613	m_b[8] = m_ui.b8;
1614	m_b[9] = m_ui.b9;
1615	m_b[10] = m_ui.bA;
1616	m_b[11] = m_ui.bB;
1617	m_b[12] = m_ui.bC;
1618	m_b[13] = m_ui.bD;
1619	m_b[14] = m_ui.bE;
1620	m_b[15] = m_ui.bF;
1621
1622	QWidget* l[16] = {
1623		m_ui.l0,
1624		m_ui.l1,
1625		m_ui.l2,
1626		m_ui.l3,
1627		m_ui.l4,
1628		m_ui.l5,
1629		m_ui.l6,
1630		m_ui.l7,
1631		m_ui.l8,
1632		m_ui.l9,
1633		m_ui.lA,
1634		m_ui.lB,
1635		m_ui.lC,
1636		m_ui.lD,
1637		m_ui.lE,
1638		m_ui.lF
1639	};
1640
1641	for (int i = 0; i < (8 << m_width); ++i) {
1642		connect(m_b[i], &QAbstractButton::toggled, this, &IOViewer::bitFlipped);
1643	}
1644
1645	for (int i = (8 << m_width) ; i < 16; ++i) {
1646		m_b[i]->hide();
1647		l[i]->hide();
1648	}
1649
1650	selectRegister(0);
1651
1652	connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
1653}
1654
1655void IOViewer::updateRegister() {
1656	{
1657		CoreController::Interrupter interrupter(m_controller);
1658		mCore* core = m_controller->thread()->core;
1659		switch (m_width) {
1660		case 0:
1661			m_value = core->rawRead8(core, m_base + m_register, -1);
1662			break;
1663		case 1:
1664			m_value = core->rawRead16(core, m_base + m_register, -1);
1665			break;
1666		}
1667	}
1668
1669	for (int i = 0; i < (8 << m_width); ++i) {
1670		QSignalBlocker blocker(m_b[i]);
1671		m_b[i]->setChecked(m_value & (1 << i));
1672	}
1673	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, (2 << m_width), 16, QChar('0')).toUpper());
1674	emit valueChanged();
1675}
1676
1677void IOViewer::bitFlipped() {
1678	m_value = 0;
1679	for (int i = 0; i < (8 << m_width); ++i) {
1680		m_value |= m_b[i]->isChecked() << i;
1681	}
1682	m_ui.regValue->setText("0x" + QString("%1").arg(m_value, (2 << m_width), 16, QChar('0')).toUpper());
1683	emit valueChanged();
1684}
1685
1686void IOViewer::writeback() {
1687	{
1688		CoreController::Interrupter interrupter(m_controller);
1689		GBAIOWrite(static_cast<GBA*>(m_controller->thread()->core->board), m_register, m_value);
1690	}
1691	updateRegister();
1692}
1693
1694void IOViewer::selectRegister(int address) {
1695	m_register = address;
1696	QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
1697	if (box) {
1698		// I can't believe there isn't a real way to do this...
1699		while (!box->isEmpty()) {
1700			QLayoutItem* item = box->takeAt(0);
1701			if (item->widget()) {
1702				delete item->widget();
1703			}
1704			delete item;
1705		}
1706	} else {
1707		box = new QGridLayout;
1708	}
1709	if (registerDescriptions(m_controller->platform()).count() <= address >> m_width) {
1710		return;
1711	}
1712	const RegisterDescription& description = registerDescriptions(m_controller->platform()).at(address >> m_width);
1713	int i = 0;
1714	for (const RegisterItem& ri : description) {
1715		QLabel* label = new QLabel(ri.description);
1716		box->addWidget(label, i, 0);
1717		if (ri.size == 1) {
1718			QCheckBox* check = new QCheckBox;
1719			check->setEnabled(!ri.readonly);
1720			box->addWidget(check, i, 1, Qt::AlignRight);
1721			connect(check, &QAbstractButton::toggled, m_b[ri.start], &QAbstractButton::setChecked);
1722			connect(this, &IOViewer::valueChanged, check, [check, this, &ri] {
1723				QSignalBlocker blocker(check);
1724				check->setChecked(bool(m_value & (1 << ri.start)));
1725			});
1726		} else if (ri.items.empty()) {
1727			QSpinBox* sbox = new QSpinBox;
1728			sbox->setEnabled(!ri.readonly);
1729			sbox->setMaximum((1 << ri.size) - 1);
1730			sbox->setAccelerated(true);
1731			box->addWidget(sbox, i, 1, Qt::AlignRight);
1732
1733			connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this, &ri](int v) {
1734				for (int o = 0; o < ri.size; ++o) {
1735					QSignalBlocker blocker(m_b[o + ri.start]);
1736					m_b[o + ri.start]->setChecked(v & (1 << o));
1737				}
1738				bitFlipped();
1739			});
1740
1741			connect(this, &IOViewer::valueChanged, sbox, [sbox, this, &ri]() {
1742				QSignalBlocker blocker(sbox);
1743				int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1744				sbox->setValue(v);
1745			});
1746		} else {
1747			QComboBox* cbox = new QComboBox;
1748			cbox->setEnabled(!ri.readonly);
1749			++i;
1750			box->addWidget(cbox, i, 0, 1, 2, Qt::AlignRight);
1751			for (int o = 0; o < 1 << ri.size; ++o) {
1752				if (ri.items.at(o).isNull()) {
1753					continue;
1754				}
1755				cbox->addItem(ri.items.at(o), o);
1756			}
1757
1758			connect(cbox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [cbox, this, &ri](int index) {
1759				unsigned v = cbox->itemData(index).toUInt();
1760				for (int o = 0; o < ri.size; ++o) {
1761					QSignalBlocker blocker(m_b[o + ri.start]);
1762					m_b[o + ri.start]->setChecked(v & (1 << o));
1763				}
1764				bitFlipped();
1765			});
1766
1767			connect(this, &IOViewer::valueChanged, cbox, [cbox, this, &ri]() {
1768				QSignalBlocker blocker(cbox);
1769				unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
1770				for (int i = 0; i < 1 << ri.size; ++i) {
1771					if (cbox->itemData(i) == v) {
1772						cbox->setCurrentIndex(i);
1773						break;
1774					}
1775				}
1776			});
1777		}
1778		++i;
1779	}
1780	m_ui.regDescription->setLayout(box);
1781	updateRegister();
1782}
1783
1784void IOViewer::selectRegister() {
1785	selectRegister(m_ui.regSelect->currentData().toUInt());
1786}
1787
1788void IOViewer::buttonPressed(QAbstractButton* button) {
1789	switch (m_ui.buttonBox->standardButton(button)) {
1790	case QDialogButtonBox::Reset:
1791		updateRegister();
1792	 	break;
1793	case QDialogButtonBox::Apply:
1794	 	writeback();
1795	 	break;
1796	default:
1797		break;
1798	}
1799}