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 <QFontDatabase>
11#include <QVBoxLayout>
12
13extern "C" {
14#include "gba/io.h"
15}
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("CGB Mode"), 3, 1, true },
30 { tr("Frame select"), 4 },
31 { tr("Unlocked HBlank"), 5 },
32 { tr("Linear OBJ tile mapping"), 6 },
33 { tr("Force blank screen"), 7 },
34 { tr("Enable background 0"), 8 },
35 { tr("Enable background 1"), 9 },
36 { tr("Enable background 2"), 10 },
37 { tr("Enable background 3"), 11 },
38 { tr("Enable OBJ"), 12 },
39 { tr("Enable Window 0"), 13 },
40 { tr("Enable Window 1"), 14 },
41 { tr("Enable OBJ Window"), 15 },
42 });
43 // 0x04000002: Green swap (undocumented and unimplemented)
44 s_registers.append(RegisterDescription());
45 // 0x04000004: DISPSTAT
46 s_registers.append({
47 { tr("Currently in VBlank"), 0, 1, true },
48 { tr("Currently in HBlank"), 1, 1, true },
49 { tr("Currently in VCounter"), 2, 1, true },
50 { tr("Enable VBlank IRQ generation"), 3 },
51 { tr("Enable HBlank IRQ generation"), 4 },
52 { tr("Enable VCounter IRQ generation"), 5 },
53 { tr("VCounter scanline"), 8, 8 },
54 });
55 // 0x04000006: VCOUNT
56 s_registers.append({
57 { tr("Current scanline"), 0, 8, true },
58 });
59 // 0x04000008: BG0CNT
60 s_registers.append({
61 { tr("Priority"), 0, 2 },
62 { tr("Tile data base (* 16kB)"), 2, 2 },
63 { tr("Enable mosaic"), 3 },
64 { tr("Enable 256-color"), 3 },
65 { tr("Tile map base (* 2kB)"), 8, 5 },
66 { tr("Background dimensions"), 14, 2 },
67 });
68 // 0x0400000A: BG1CNT
69 s_registers.append({
70 { tr("Priority"), 0, 2 },
71 { tr("Tile data base (* 16kB)"), 2, 2 },
72 { tr("Enable mosaic"), 3 },
73 { tr("Enable 256-color"), 3 },
74 { tr("Tile map base (* 2kB)"), 8, 5 },
75 { tr("Background dimensions"), 14, 2 },
76 });
77 // 0x0400000C: BG2CNT
78 s_registers.append({
79 { tr("Priority"), 0, 2 },
80 { tr("Tile data base (* 16kB)"), 2, 2 },
81 { tr("Enable mosaic"), 3 },
82 { tr("Enable 256-color"), 3 },
83 { tr("Tile map base (* 2kB)"), 8, 5 },
84 { tr("Overflow wraps"), 9 },
85 { tr("Background dimensions"), 14, 2 },
86 });
87 // 0x0400000E: BG3CNT
88 s_registers.append({
89 { tr("Priority"), 0, 2 },
90 { tr("Tile data base (* 16kB)"), 2, 2 },
91 { tr("Enable mosaic"), 3 },
92 { tr("Enable 256-color"), 3 },
93 { tr("Tile map base (* 2kB)"), 8, 5 },
94 { tr("Overflow wraps"), 9 },
95 { tr("Background dimensions"), 14, 2 },
96 });
97 // 0x04000010: BG0HOFS
98 s_registers.append({
99 { tr("Horizontal offset"), 0, 9 },
100 });
101 // 0x04000012: BG0VOFS
102 s_registers.append({
103 { tr("Vertical offset"), 0, 9 },
104 });
105 // 0x04000014: BG1HOFS
106 s_registers.append({
107 { tr("Horizontal offset"), 0, 9 },
108 });
109 // 0x04000016: BG1VOFS
110 s_registers.append({
111 { tr("Vertical offset"), 0, 9 },
112 });
113 // 0x04000018: BG2HOFS
114 s_registers.append({
115 { tr("Horizontal offset"), 0, 9 },
116 });
117 // 0x0400001A: BG2VOFS
118 s_registers.append({
119 { tr("Vertical offset"), 0, 9 },
120 });
121 // 0x0400001C: BG3HOFS
122 s_registers.append({
123 { tr("Horizontal offset"), 0, 9 },
124 });
125 // 0x0400001E: BG3VOFS
126 s_registers.append({
127 { tr("Vertical offset"), 0, 9 },
128 });
129 return s_registers;
130}
131
132IOViewer::IOViewer(GameController* controller, QWidget* parent)
133 : QDialog(parent)
134 , m_controller(controller)
135{
136 m_ui.setupUi(this);
137
138 for (unsigned i = 0; i < REG_MAX >> 1; ++i) {
139 const char* reg = GBAIORegisterNames[i];
140 if (!reg) {
141 continue;
142 }
143 m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
144 }
145
146 const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
147 m_ui.regValue->setFont(font);
148
149 connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*)));
150 connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close()));
151 connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister()));
152
153 m_b[0] = m_ui.b0;
154 m_b[1] = m_ui.b1;
155 m_b[2] = m_ui.b2;
156 m_b[3] = m_ui.b3;
157 m_b[4] = m_ui.b4;
158 m_b[5] = m_ui.b5;
159 m_b[6] = m_ui.b6;
160 m_b[7] = m_ui.b7;
161 m_b[8] = m_ui.b8;
162 m_b[9] = m_ui.b9;
163 m_b[10] = m_ui.bA;
164 m_b[11] = m_ui.bB;
165 m_b[12] = m_ui.bC;
166 m_b[13] = m_ui.bD;
167 m_b[14] = m_ui.bE;
168 m_b[15] = m_ui.bF;
169
170 for (int i = 0; i < 16; ++i) {
171 connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped()));
172 }
173
174 selectRegister(0);
175}
176
177void IOViewer::updateRegister() {
178 m_value = 0;
179 uint16_t value = 0;
180 m_controller->threadInterrupt();
181 if (m_controller->isLoaded()) {
182 value = GBAIORead(m_controller->thread()->gba, m_register);
183 }
184 m_controller->threadContinue();
185
186 for (int i = 0; i < 16; ++i) {
187 m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked);
188 }
189 m_value = value;
190}
191
192void IOViewer::bitFlipped() {
193 m_value = 0;
194 for (int i = 0; i < 16; ++i) {
195 m_value |= m_b[i]->isChecked() << i;
196 }
197 m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
198}
199
200void IOViewer::writeback() {
201 m_controller->threadInterrupt();
202 if (m_controller->isLoaded()) {
203 GBAIOWrite(m_controller->thread()->gba, m_register, m_value);
204 }
205 m_controller->threadContinue();
206 updateRegister();
207}
208
209void IOViewer::selectRegister(unsigned address) {
210 m_register = address;
211 QLayout* box = m_ui.regDescription->layout();
212 if (box) {
213 // I can't believe there isn't a real way to do this...
214 while (!box->isEmpty()) {
215 QLayoutItem* item = box->takeAt(0);
216 if (item->widget()) {
217 delete item->widget();
218 }
219 delete item;
220 }
221 } else {
222 box = new QVBoxLayout;
223 }
224 if (registerDescriptions().count() > address >> 1) {
225 // TODO: Remove the check when done filling in register information
226 const RegisterDescription& description = registerDescriptions().at(address >> 1);
227 for (const RegisterItem& ri : description) {
228 QCheckBox* check = new QCheckBox;
229 check->setText(ri.description);
230 check->setEnabled(!ri.readonly);
231 box->addWidget(check);
232 connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool)));
233 connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool)));
234 }
235 }
236 m_ui.regDescription->setLayout(box);
237 updateRegister();
238}
239
240void IOViewer::selectRegister() {
241 selectRegister(m_ui.regSelect->currentData().toUInt());
242}
243
244void IOViewer::buttonPressed(QAbstractButton* button) {
245 switch (m_ui.buttonBox->standardButton(button)) {
246 case QDialogButtonBox::Reset:
247 updateRegister();
248 break;
249 case QDialogButtonBox::Apply:
250 writeback();
251 break;
252 default:
253 break;
254 }
255}