1 // Copyright 2018 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "DolphinQt/Debugger/MemoryViewWidget.h"
6
7 #include <QApplication>
8 #include <QClipboard>
9 #include <QHeaderView>
10 #include <QMenu>
11 #include <QMouseEvent>
12 #include <QScrollBar>
13
14 #include <cctype>
15 #include <cmath>
16
17 #include "Common/StringUtil.h"
18 #include "Core/Core.h"
19 #include "Core/HW/AddressSpace.h"
20 #include "Core/PowerPC/BreakPoints.h"
21 #include "Core/PowerPC/PowerPC.h"
22 #include "DolphinQt/Host.h"
23 #include "DolphinQt/Resources.h"
24 #include "DolphinQt/Settings.h"
25
26 // "Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of
27 // 120; i.e., 120 units * 1/8 = 15 degrees." (http://doc.qt.io/qt-5/qwheelevent.html#angleDelta)
28 constexpr double SCROLL_FRACTION_DEGREES = 15.;
29
MemoryViewWidget(QWidget * parent)30 MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QTableWidget(parent)
31 {
32 horizontalHeader()->hide();
33 verticalHeader()->hide();
34 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
35 setShowGrid(false);
36
37 setFont(Settings::Instance().GetDebugFont());
38
39 connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &QWidget::setFont);
40 connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { Update(); });
41 connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::Update);
42 connect(this, &MemoryViewWidget::customContextMenuRequested, this,
43 &MemoryViewWidget::OnContextMenu);
44 connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update);
45
46 setContextMenuPolicy(Qt::CustomContextMenu);
47
48 Update();
49 }
50
GetColumnCount(MemoryViewWidget::Type type)51 static int GetColumnCount(MemoryViewWidget::Type type)
52 {
53 switch (type)
54 {
55 case MemoryViewWidget::Type::ASCII:
56 case MemoryViewWidget::Type::U8:
57 return 16;
58 case MemoryViewWidget::Type::U16:
59 return 8;
60 case MemoryViewWidget::Type::U32:
61 case MemoryViewWidget::Type::Float32:
62 return 4;
63 default:
64 return 0;
65 }
66 }
67
Update()68 void MemoryViewWidget::Update()
69 {
70 clearSelection();
71
72 setColumnCount(3 + GetColumnCount(m_type));
73
74 if (rowCount() == 0)
75 setRowCount(1);
76
77 setRowHeight(0, 24);
78
79 const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
80
81 // Calculate (roughly) how many rows will fit in our table
82 int rows = std::round((height() / static_cast<float>(rowHeight(0))) - 0.25);
83
84 setRowCount(rows);
85
86 for (int i = 0; i < rows; i++)
87 {
88 setRowHeight(i, 24);
89
90 u32 addr = m_address - ((rowCount() / 2) * 16) + i * 16;
91
92 auto* bp_item = new QTableWidgetItem;
93 bp_item->setFlags(Qt::ItemIsEnabled);
94 bp_item->setData(Qt::UserRole, addr);
95
96 setItem(i, 0, bp_item);
97
98 auto* addr_item = new QTableWidgetItem(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
99
100 addr_item->setData(Qt::UserRole, addr);
101 addr_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
102
103 setItem(i, 1, addr_item);
104
105 if (addr == m_address)
106 addr_item->setSelected(true);
107
108 if (Core::GetState() != Core::State::Paused || !accessors->IsValidAddress(addr))
109 {
110 for (int c = 2; c < columnCount(); c++)
111 {
112 auto* item = new QTableWidgetItem(QStringLiteral("-"));
113 item->setFlags(Qt::ItemIsEnabled);
114 item->setData(Qt::UserRole, addr);
115
116 setItem(i, c, item);
117 }
118
119 continue;
120 }
121
122 if (m_address_space == AddressSpace::Type::Effective)
123 {
124 auto* description_item = new QTableWidgetItem(
125 QString::fromStdString(PowerPC::debug_interface.GetDescription(addr)));
126
127 description_item->setForeground(Qt::blue);
128 description_item->setFlags(Qt::ItemIsEnabled);
129
130 setItem(i, columnCount() - 1, description_item);
131 }
132 bool row_breakpoint = true;
133
134 auto update_values = [&](auto value_to_string) {
135 for (int c = 0; c < GetColumnCount(m_type); c++)
136 {
137 auto* hex_item = new QTableWidgetItem;
138 hex_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
139 const u32 address = addr + c * (16 / GetColumnCount(m_type));
140
141 if (m_address_space == AddressSpace::Type::Effective &&
142 PowerPC::memchecks.OverlapsMemcheck(address, 16 / GetColumnCount(m_type)))
143 {
144 hex_item->setBackground(Qt::red);
145 }
146 else
147 {
148 row_breakpoint = false;
149 }
150 setItem(i, 2 + c, hex_item);
151
152 if (accessors->IsValidAddress(address))
153 {
154 hex_item->setText(value_to_string(address));
155 hex_item->setData(Qt::UserRole, address);
156 }
157 else
158 {
159 hex_item->setFlags({});
160 hex_item->setText(QStringLiteral("-"));
161 }
162 }
163 };
164 switch (m_type)
165 {
166 case Type::U8:
167 update_values([&accessors](u32 address) {
168 const u8 value = accessors->ReadU8(address);
169 return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0'));
170 });
171 break;
172 case Type::ASCII:
173 update_values([&accessors](u32 address) {
174 const char value = accessors->ReadU8(address);
175 return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} :
176 QString{QChar::fromLatin1('.')};
177 });
178 break;
179 case Type::U16:
180 update_values([&accessors](u32 address) {
181 const u16 value = accessors->ReadU16(address);
182 return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0'));
183 });
184 break;
185 case Type::U32:
186 update_values([&accessors](u32 address) {
187 const u32 value = accessors->ReadU32(address);
188 return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
189 });
190 break;
191 case Type::Float32:
192 update_values(
193 [&accessors](u32 address) { return QString::number(accessors->ReadF32(address)); });
194 break;
195 }
196
197 if (row_breakpoint)
198 {
199 bp_item->setData(Qt::DecorationRole,
200 Resources::GetScaledThemeIcon("debugger_breakpoint").pixmap(QSize(24, 24)));
201 }
202 }
203
204 setColumnWidth(0, 24 + 5);
205 for (int i = 1; i < columnCount(); i++)
206 {
207 resizeColumnToContents(i);
208 // Add some extra spacing because the default width is too small in most cases
209 setColumnWidth(i, columnWidth(i) * 1.1);
210 }
211
212 viewport()->update();
213 update();
214 }
215
SetAddressSpace(AddressSpace::Type address_space)216 void MemoryViewWidget::SetAddressSpace(AddressSpace::Type address_space)
217 {
218 if (m_address_space == address_space)
219 {
220 return;
221 }
222
223 m_address_space = address_space;
224 Update();
225 }
226
GetAddressSpace() const227 AddressSpace::Type MemoryViewWidget::GetAddressSpace() const
228 {
229 return m_address_space;
230 }
231
SetType(Type type)232 void MemoryViewWidget::SetType(Type type)
233 {
234 if (m_type == type)
235 return;
236
237 m_type = type;
238 Update();
239 }
240
SetBPType(BPType type)241 void MemoryViewWidget::SetBPType(BPType type)
242 {
243 m_bp_type = type;
244 }
245
SetAddress(u32 address)246 void MemoryViewWidget::SetAddress(u32 address)
247 {
248 if (m_address == address)
249 return;
250
251 m_address = address;
252 Update();
253 }
254
SetBPLoggingEnabled(bool enabled)255 void MemoryViewWidget::SetBPLoggingEnabled(bool enabled)
256 {
257 m_do_log = enabled;
258 }
259
resizeEvent(QResizeEvent *)260 void MemoryViewWidget::resizeEvent(QResizeEvent*)
261 {
262 Update();
263 }
264
keyPressEvent(QKeyEvent * event)265 void MemoryViewWidget::keyPressEvent(QKeyEvent* event)
266 {
267 switch (event->key())
268 {
269 case Qt::Key_Up:
270 m_address -= 16;
271 Update();
272 return;
273 case Qt::Key_Down:
274 m_address += 16;
275 Update();
276 return;
277 case Qt::Key_PageUp:
278 m_address -= rowCount() * 16;
279 Update();
280 return;
281 case Qt::Key_PageDown:
282 m_address += rowCount() * 16;
283 Update();
284 return;
285 default:
286 QWidget::keyPressEvent(event);
287 break;
288 }
289 }
290
GetContextAddress() const291 u32 MemoryViewWidget::GetContextAddress() const
292 {
293 return m_context_address;
294 }
295
ToggleRowBreakpoint(bool row)296 void MemoryViewWidget::ToggleRowBreakpoint(bool row)
297 {
298 TMemCheck check;
299
300 const u32 addr = row ? GetContextAddress() & 0xFFFFFFF0 : GetContextAddress();
301 const auto length = row ? 16 : (16 / GetColumnCount(m_type));
302
303 if (m_address_space == AddressSpace::Type::Effective)
304 {
305 if (!PowerPC::memchecks.OverlapsMemcheck(addr, length))
306 {
307 check.start_address = addr;
308 check.end_address = check.start_address + length - 1;
309 check.is_ranged = length > 0;
310 check.is_break_on_read = (m_bp_type == BPType::ReadOnly || m_bp_type == BPType::ReadWrite);
311 check.is_break_on_write = (m_bp_type == BPType::WriteOnly || m_bp_type == BPType::ReadWrite);
312 check.log_on_hit = m_do_log;
313 check.break_on_hit = true;
314
315 PowerPC::memchecks.Add(check);
316 }
317 else
318 {
319 PowerPC::memchecks.Remove(addr);
320 }
321 }
322
323 emit BreakpointsChanged();
324 Update();
325 }
326
ToggleBreakpoint()327 void MemoryViewWidget::ToggleBreakpoint()
328 {
329 ToggleRowBreakpoint(false);
330 }
331
wheelEvent(QWheelEvent * event)332 void MemoryViewWidget::wheelEvent(QWheelEvent* event)
333 {
334 auto delta =
335 -static_cast<int>(std::round((event->angleDelta().y() / (SCROLL_FRACTION_DEGREES * 8))));
336
337 if (delta == 0)
338 return;
339
340 m_address += delta * 16;
341 Update();
342 }
343
mousePressEvent(QMouseEvent * event)344 void MemoryViewWidget::mousePressEvent(QMouseEvent* event)
345 {
346 auto* item = itemAt(event->pos());
347 if (item == nullptr)
348 return;
349
350 const u32 addr = item->data(Qt::UserRole).toUInt();
351
352 m_context_address = addr;
353
354 switch (event->button())
355 {
356 case Qt::LeftButton:
357 if (column(item) == 0)
358 ToggleRowBreakpoint(true);
359 else
360 SetAddress(addr & 0xFFFFFFF0);
361
362 Update();
363 break;
364 default:
365 break;
366 }
367 }
368
OnCopyAddress()369 void MemoryViewWidget::OnCopyAddress()
370 {
371 u32 addr = GetContextAddress();
372 QApplication::clipboard()->setText(QStringLiteral("%1").arg(addr, 8, 16, QLatin1Char('0')));
373 }
374
OnCopyHex()375 void MemoryViewWidget::OnCopyHex()
376 {
377 u32 addr = GetContextAddress();
378
379 const auto length = 16 / GetColumnCount(m_type);
380
381 const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
382 u64 value = accessors->ReadU64(addr);
383
384 QApplication::clipboard()->setText(
385 QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2));
386 }
387
OnContextMenu()388 void MemoryViewWidget::OnContextMenu()
389 {
390 auto* menu = new QMenu(this);
391
392 menu->addAction(tr("Copy Address"), this, &MemoryViewWidget::OnCopyAddress);
393
394 auto* copy_hex = menu->addAction(tr("Copy Hex"), this, &MemoryViewWidget::OnCopyHex);
395
396 const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space);
397 copy_hex->setEnabled(Core::GetState() != Core::State::Uninitialized &&
398 accessors->IsValidAddress(GetContextAddress()));
399
400 menu->addSeparator();
401
402 menu->addAction(tr("Show in code"), this, [this] { emit ShowCode(GetContextAddress()); });
403
404 menu->addSeparator();
405
406 menu->addAction(tr("Toggle Breakpoint"), this, &MemoryViewWidget::ToggleBreakpoint);
407
408 menu->exec(QCursor::pos());
409 }
410