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