mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #4847 from zhaowenlan1779/ipc-debugger
core, citra_qt: IPC Recorder
This commit is contained in:
		
						commit
						223bfc9a2a
					
				
					 23 changed files with 1112 additions and 15 deletions
				
			
		|  | @ -90,6 +90,12 @@ add_executable(citra-qt | |||
|     debugger/graphics/graphics_tracing.h | ||||
|     debugger/graphics/graphics_vertex_shader.cpp | ||||
|     debugger/graphics/graphics_vertex_shader.h | ||||
|     debugger/ipc/record_dialog.cpp | ||||
|     debugger/ipc/record_dialog.h | ||||
|     debugger/ipc/record_dialog.ui | ||||
|     debugger/ipc/recorder.cpp | ||||
|     debugger/ipc/recorder.h | ||||
|     debugger/ipc/recorder.ui | ||||
|     debugger/lle_service_modules.cpp | ||||
|     debugger/lle_service_modules.h | ||||
|     debugger/profiler.cpp | ||||
|  |  | |||
							
								
								
									
										64
									
								
								src/citra_qt/debugger/ipc/record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/citra_qt/debugger/ipc/record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,64 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include "citra_qt/debugger/ipc/record_dialog.h" | ||||
| #include "common/assert.h" | ||||
| #include "core/hle/kernel/ipc_debugger/recorder.h" | ||||
| #include "ui_record_dialog.h" | ||||
| 
 | ||||
| QString RecordDialog::FormatObject(const IPCDebugger::ObjectInfo& object) const { | ||||
|     if (object.id == -1) { | ||||
|         return tr("null"); | ||||
|     } | ||||
| 
 | ||||
|     return QStringLiteral("%1 (0x%2)") | ||||
|         .arg(QString::fromStdString(object.name)) | ||||
|         .arg(object.id, 8, 16, QLatin1Char('0')); | ||||
| } | ||||
| 
 | ||||
| QString RecordDialog::FormatCmdbuf(const std::vector<u32>& cmdbuf) const { | ||||
|     QString result; | ||||
|     for (std::size_t i = 0; i < cmdbuf.size(); ++i) { | ||||
|         result.append( | ||||
|             QStringLiteral("[%1]: 0x%2\n").arg(i).arg(cmdbuf[i], 8, 16, QLatin1Char('0'))); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| RecordDialog::RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record, | ||||
|                            const QString& service, const QString& function) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::RecordDialog>()) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     ui->clientProcess->setText(FormatObject(record.client_process)); | ||||
|     ui->clientThread->setText(FormatObject(record.client_thread)); | ||||
|     ui->clientSession->setText(FormatObject(record.client_session)); | ||||
| 
 | ||||
|     ui->serverProcess->setText(FormatObject(record.server_process)); | ||||
|     ui->serverThread->setText(FormatObject(record.server_thread)); | ||||
|     ui->serverSession->setText(FormatObject(record.server_session)); | ||||
| 
 | ||||
|     ui->clientPort->setText(FormatObject(record.client_port)); | ||||
|     ui->service->setText(std::move(service)); | ||||
|     ui->function->setText(std::move(function)); | ||||
| 
 | ||||
|     cmdbufs = {record.untranslated_request_cmdbuf, record.translated_request_cmdbuf, | ||||
|                record.untranslated_reply_cmdbuf, record.translated_reply_cmdbuf}; | ||||
| 
 | ||||
|     UpdateCmdbufDisplay(); | ||||
| 
 | ||||
|     connect(ui->cmdbufSelection, qOverload<int>(&QComboBox::currentIndexChanged), this, | ||||
|             &RecordDialog::UpdateCmdbufDisplay); | ||||
| } | ||||
| 
 | ||||
| RecordDialog::~RecordDialog() = default; | ||||
| 
 | ||||
| void RecordDialog::UpdateCmdbufDisplay() { | ||||
|     int index = ui->cmdbufSelection->currentIndex(); | ||||
| 
 | ||||
|     ASSERT_MSG(0 <= index && index <= 3, "Index out of bound"); | ||||
|     ui->cmdbuf->setPlainText(FormatCmdbuf(cmdbufs[index])); | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/citra_qt/debugger/ipc/record_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/citra_qt/debugger/ipc/record_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <array> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <QDialog> | ||||
| #include "common/common_types.h" | ||||
| 
 | ||||
| namespace IPCDebugger { | ||||
| struct ObjectInfo; | ||||
| struct RequestRecord; | ||||
| } // namespace IPCDebugger
 | ||||
| 
 | ||||
| namespace Ui { | ||||
| class RecordDialog; | ||||
| } | ||||
| 
 | ||||
| class RecordDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record, | ||||
|                           const QString& service, const QString& function); | ||||
|     ~RecordDialog() override; | ||||
| 
 | ||||
| private: | ||||
|     QString FormatObject(const IPCDebugger::ObjectInfo& object) const; | ||||
|     QString FormatCmdbuf(const std::vector<u32>& cmdbuf) const; | ||||
|     void UpdateCmdbufDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::RecordDialog> ui; | ||||
|     std::array<std::vector<u32>, 4> cmdbufs; | ||||
| }; | ||||
							
								
								
									
										245
									
								
								src/citra_qt/debugger/ipc/record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/citra_qt/debugger/ipc/record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,245 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>RecordDialog</class> | ||||
|  <widget class="QDialog" name="RecordDialog"> | ||||
|   <property name="windowTitle"> | ||||
|    <string>View Record</string> | ||||
|   </property> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>800</width> | ||||
|     <height>500</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QGroupBox"> | ||||
|        <property name="title"> | ||||
|         <string>Client</string> | ||||
|        </property> | ||||
|        <layout class="QFormLayout"> | ||||
|         <item row="0" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Process:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="0" column="1"> | ||||
|          <widget class="QLineEdit" name="clientProcess"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="1" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Thread:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="1" column="1"> | ||||
|          <widget class="QLineEdit" name="clientThread"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="2" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Session:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="2" column="1"> | ||||
|          <widget class="QLineEdit" name="clientSession"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QGroupBox"> | ||||
|        <property name="title"> | ||||
|         <string>Server</string> | ||||
|        </property> | ||||
|        <layout class="QFormLayout"> | ||||
|         <item row="0" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Process:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="0" column="1"> | ||||
|          <widget class="QLineEdit" name="serverProcess"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="1" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Thread:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="1" column="1"> | ||||
|          <widget class="QLineEdit" name="serverThread"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="2" column="0"> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Session:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item row="2" column="1"> | ||||
|          <widget class="QLineEdit" name="serverSession"> | ||||
|           <property name="readOnly"> | ||||
|            <bool>true</bool> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>General</string> | ||||
|      </property> | ||||
|      <layout class="QFormLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Client Port:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QLineEdit" name="clientPort"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Service:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="service"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Function:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QLineEdit" name="function"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Command Buffer</string> | ||||
|      </property> | ||||
|      <layout class="QVBoxLayout"> | ||||
|       <item> | ||||
|        <layout class="QHBoxLayout"> | ||||
|         <item> | ||||
|          <widget class="QLabel"> | ||||
|           <property name="text"> | ||||
|            <string>Select:</string> | ||||
|           </property> | ||||
|          </widget> | ||||
|         </item> | ||||
|         <item> | ||||
|          <widget class="QComboBox" name="cmdbufSelection"> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Request Untranslated</string> | ||||
|            </property> | ||||
|           </item> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Request Translated</string> | ||||
|            </property> | ||||
|           </item> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Reply Untranslated</string> | ||||
|            </property> | ||||
|           </item> | ||||
|           <item> | ||||
|            <property name="text"> | ||||
|             <string>Reply Translated</string> | ||||
|            </property> | ||||
|           </item> | ||||
|          </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QPlainTextEdit" name="cmdbuf"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <spacer> | ||||
|        <property name="orientation"> | ||||
|         <enum>Qt::Horizontal</enum> | ||||
|        </property> | ||||
|       </spacer> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QPushButton" name="okButton"> | ||||
|        <property name="text"> | ||||
|         <string>OK</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
							
								
								
									
										183
									
								
								src/citra_qt/debugger/ipc/recorder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/citra_qt/debugger/ipc/recorder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,183 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QString> | ||||
| #include <QTreeWidgetItem> | ||||
| #include <fmt/format.h> | ||||
| #include "citra_qt/debugger/ipc/record_dialog.h" | ||||
| #include "citra_qt/debugger/ipc/recorder.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/core.h" | ||||
| #include "core/hle/kernel/ipc_debugger/recorder.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/service/sm/sm.h" | ||||
| #include "ui_recorder.h" | ||||
| 
 | ||||
| IPCRecorderWidget::IPCRecorderWidget(QWidget* parent) | ||||
|     : QDockWidget(parent), ui(std::make_unique<Ui::IPCRecorder>()) { | ||||
| 
 | ||||
|     ui->setupUi(this); | ||||
|     qRegisterMetaType<IPCDebugger::RequestRecord>(); | ||||
| 
 | ||||
|     connect(ui->enabled, &QCheckBox::stateChanged, | ||||
|             [this](int new_state) { SetEnabled(new_state == Qt::Checked); }); | ||||
|     connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear); | ||||
|     connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll); | ||||
|     connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog); | ||||
|     connect(this, &IPCRecorderWidget::EntryUpdated, this, &IPCRecorderWidget::OnEntryUpdated); | ||||
| } | ||||
| 
 | ||||
| IPCRecorderWidget::~IPCRecorderWidget() = default; | ||||
| 
 | ||||
| void IPCRecorderWidget::OnEmulationStarting() { | ||||
|     Clear(); | ||||
|     id_offset = 1; | ||||
| 
 | ||||
|     // Update the enabled status when the system is powered on.
 | ||||
|     SetEnabled(ui->enabled->isChecked()); | ||||
| } | ||||
| 
 | ||||
| QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record) const { | ||||
|     switch (record.status) { | ||||
|     case IPCDebugger::RequestStatus::Invalid: | ||||
|         return tr("Invalid"); | ||||
|     case IPCDebugger::RequestStatus::Sent: | ||||
|         return tr("Sent"); | ||||
|     case IPCDebugger::RequestStatus::Handling: | ||||
|         return tr("Handling"); | ||||
|     case IPCDebugger::RequestStatus::Handled: | ||||
|         if (record.translated_reply_cmdbuf[1] == RESULT_SUCCESS.raw) { | ||||
|             return tr("Success"); | ||||
|         } | ||||
|         return tr("Error"); | ||||
|     case IPCDebugger::RequestStatus::HLEUnimplemented: | ||||
|         return tr("HLE Unimplemented"); | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::OnEntryUpdated(IPCDebugger::RequestRecord record) { | ||||
|     if (record.id < id_offset) { // The record has already been deleted by 'Clear'
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     QString service = GetServiceName(record); | ||||
|     if (record.status == IPCDebugger::RequestStatus::Handling || | ||||
|         record.status == IPCDebugger::RequestStatus::Handled || | ||||
|         record.status == IPCDebugger::RequestStatus::HLEUnimplemented) { | ||||
| 
 | ||||
|         service = QStringLiteral("%1 (%2)").arg(service, record.is_hle ? tr("HLE") : tr("LLE")); | ||||
|     } | ||||
| 
 | ||||
|     QTreeWidgetItem item{ | ||||
|         {QString::number(record.id), GetStatusStr(record), service, GetFunctionName(record)}}; | ||||
| 
 | ||||
|     const int row_id = record.id - id_offset; | ||||
|     if (ui->main->invisibleRootItem()->childCount() > row_id) { | ||||
|         records[row_id] = record; | ||||
|         (*ui->main->invisibleRootItem()->child(row_id)) = item; | ||||
|     } else { | ||||
|         records.emplace_back(record); | ||||
|         ui->main->invisibleRootItem()->addChild(new QTreeWidgetItem(item)); | ||||
|     } | ||||
| 
 | ||||
|     if (record.status == IPCDebugger::RequestStatus::HLEUnimplemented || | ||||
|         (record.status == IPCDebugger::RequestStatus::Handled && | ||||
|          record.translated_reply_cmdbuf[1] != RESULT_SUCCESS.raw)) { // Unimplemented / Error
 | ||||
| 
 | ||||
|         auto* item = ui->main->invisibleRootItem()->child(row_id); | ||||
|         for (int column = 0; column < item->columnCount(); ++column) { | ||||
|             item->setBackgroundColor(column, QColor::fromRgb(255, 0, 0)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     ApplyFilter(row_id); | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::SetEnabled(bool enabled) { | ||||
|     if (!Core::System::GetInstance().IsPoweredOn()) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto& ipc_recorder = Core::System::GetInstance().Kernel().GetIPCRecorder(); | ||||
|     ipc_recorder.SetEnabled(enabled); | ||||
| 
 | ||||
|     if (enabled) { | ||||
|         handle = ipc_recorder.BindCallback( | ||||
|             [this](const IPCDebugger::RequestRecord& record) { emit EntryUpdated(record); }); | ||||
|     } else if (handle) { | ||||
|         ipc_recorder.UnbindCallback(handle); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::Clear() { | ||||
|     id_offset = records.size() + 1; | ||||
| 
 | ||||
|     records.clear(); | ||||
|     ui->main->invisibleRootItem()->takeChildren(); | ||||
| } | ||||
| 
 | ||||
| QString IPCRecorderWidget::GetServiceName(const IPCDebugger::RequestRecord& record) const { | ||||
|     if (Core::System::GetInstance().IsPoweredOn() && record.client_port.id != -1) { | ||||
|         const auto service_name = | ||||
|             Core::System::GetInstance().ServiceManager().GetServiceNameByPortId( | ||||
|                 static_cast<u32>(record.client_port.id)); | ||||
| 
 | ||||
|         if (!service_name.empty()) { | ||||
|             return QString::fromStdString(service_name); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Get a similar result from the server session name
 | ||||
|     std::string session_name = record.server_session.name; | ||||
|     session_name = Common::ReplaceAll(session_name, "_Server", ""); | ||||
|     session_name = Common::ReplaceAll(session_name, "_Client", ""); | ||||
|     return QString::fromStdString(session_name); | ||||
| } | ||||
| 
 | ||||
| QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& record) const { | ||||
|     if (record.untranslated_request_cmdbuf.empty()) { // Cmdbuf is not yet available
 | ||||
|         return tr("Unknown"); | ||||
|     } | ||||
|     const QString header_code = | ||||
|         QStringLiteral("0x%1").arg(record.untranslated_request_cmdbuf[0], 8, 16, QLatin1Char('0')); | ||||
|     if (record.function_name.empty()) { | ||||
|         return header_code; | ||||
|     } | ||||
|     return QStringLiteral("%1 (%2)").arg(QString::fromStdString(record.function_name), header_code); | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::ApplyFilter(int index) { | ||||
|     auto* item = ui->main->invisibleRootItem()->child(index); | ||||
|     const QString filter = ui->filter->text(); | ||||
|     if (filter.isEmpty()) { | ||||
|         item->setHidden(false); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     for (int i = 0; i < item->columnCount(); ++i) { | ||||
|         if (item->text(i).contains(filter)) { | ||||
|             item->setHidden(false); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     item->setHidden(true); | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::ApplyFilterToAll() { | ||||
|     for (int i = 0; i < ui->main->invisibleRootItem()->childCount(); ++i) { | ||||
|         ApplyFilter(i); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void IPCRecorderWidget::OpenRecordDialog(QTreeWidgetItem* item, [[maybe_unused]] int column) { | ||||
|     int index = ui->main->invisibleRootItem()->indexOfChild(item); | ||||
| 
 | ||||
|     RecordDialog dialog(this, records[static_cast<std::size_t>(index)], item->text(2), | ||||
|                         item->text(3)); | ||||
|     dialog.exec(); | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/citra_qt/debugger/ipc/recorder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/citra_qt/debugger/ipc/recorder.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| // Copyright 2019 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <unordered_map> | ||||
| #include <QDockWidget> | ||||
| #include "core/hle/kernel/ipc_debugger/recorder.h" | ||||
| 
 | ||||
| class QTreeWidgetItem; | ||||
| 
 | ||||
| namespace Ui { | ||||
| class IPCRecorder; | ||||
| } | ||||
| 
 | ||||
| class IPCRecorderWidget : public QDockWidget { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit IPCRecorderWidget(QWidget* parent = nullptr); | ||||
|     ~IPCRecorderWidget(); | ||||
| 
 | ||||
|     void OnEmulationStarting(); | ||||
| 
 | ||||
| signals: | ||||
|     void EntryUpdated(IPCDebugger::RequestRecord record); | ||||
| 
 | ||||
| private: | ||||
|     QString GetStatusStr(const IPCDebugger::RequestRecord& record) const; | ||||
|     void OnEntryUpdated(IPCDebugger::RequestRecord record); | ||||
|     void SetEnabled(bool enabled); | ||||
|     void Clear(); | ||||
|     void ApplyFilter(int index); | ||||
|     void ApplyFilterToAll(); | ||||
|     QString GetServiceName(const IPCDebugger::RequestRecord& record) const; | ||||
|     QString GetFunctionName(const IPCDebugger::RequestRecord& record) const; | ||||
|     void OpenRecordDialog(QTreeWidgetItem* item, int column); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::IPCRecorder> ui; | ||||
|     IPCDebugger::CallbackHandle handle; | ||||
| 
 | ||||
|     // The offset between record id and row id, assuming record ids are assigned
 | ||||
|     // continuously and only the 'Clear' action can be performed, this is enough.
 | ||||
|     // The initial value is 1, which means record 1 = row 0.
 | ||||
|     int id_offset = 1; | ||||
|     std::vector<IPCDebugger::RequestRecord> records; | ||||
| }; | ||||
| 
 | ||||
| Q_DECLARE_METATYPE(IPCDebugger::RequestRecord); | ||||
							
								
								
									
										93
									
								
								src/citra_qt/debugger/ipc/recorder.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/citra_qt/debugger/ipc/recorder.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,93 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>IPCRecorder</class> | ||||
|  <widget class="QDockWidget" name="IPCRecorder"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>300</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>IPC Recorder</string> | ||||
|   </property> | ||||
|   <widget class="QWidget"> | ||||
|    <layout class="QVBoxLayout"> | ||||
|     <item> | ||||
|      <widget class="QCheckBox" name="enabled"> | ||||
|       <property name="text"> | ||||
|        <string>Enable Recording</string> | ||||
|       </property> | ||||
|      </widget> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout"> | ||||
|       <item> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Filter:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QLineEdit" name="filter"> | ||||
|         <property name="placeholderText"> | ||||
|          <string>Leave empty to disable filtering</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|     <item> | ||||
|      <widget class="QTreeWidget" name="main"> | ||||
|       <property name="alternatingRowColors"> | ||||
|        <bool>true</bool> | ||||
|       </property> | ||||
|       <column> | ||||
|        <property name="text"> | ||||
|         <string>#</string> | ||||
|        </property> | ||||
|       </column> | ||||
|       <column> | ||||
|        <property name="text"> | ||||
|         <string>Status</string> | ||||
|        </property> | ||||
|       </column> | ||||
|       <column> | ||||
|        <property name="text"> | ||||
|         <string>Service</string> | ||||
|        </property> | ||||
|       </column> | ||||
|       <column> | ||||
|        <property name="text"> | ||||
|         <string>Function</string> | ||||
|        </property> | ||||
|       </column> | ||||
|      </widget> | ||||
|     </item> | ||||
|     <item> | ||||
|      <layout class="QHBoxLayout"> | ||||
|       <item> | ||||
|        <spacer> | ||||
|         <property name="orientation"> | ||||
|          <enum>Qt::Horizontal</enum> | ||||
|         </property> | ||||
|        </spacer> | ||||
|       </item> | ||||
|       <item> | ||||
|        <widget class="QPushButton" name="clearButton"> | ||||
|         <property name="text"> | ||||
|          <string>Clear</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </item> | ||||
|    </layout> | ||||
|   </widget> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  <connections/> | ||||
| </ui> | ||||
|  | @ -40,6 +40,7 @@ | |||
| #include "citra_qt/debugger/graphics/graphics_surface.h" | ||||
| #include "citra_qt/debugger/graphics/graphics_tracing.h" | ||||
| #include "citra_qt/debugger/graphics/graphics_vertex_shader.h" | ||||
| #include "citra_qt/debugger/ipc/recorder.h" | ||||
| #include "citra_qt/debugger/lle_service_modules.h" | ||||
| #include "citra_qt/debugger/profiler.h" | ||||
| #include "citra_qt/debugger/registers.h" | ||||
|  | @ -339,6 +340,13 @@ void GMainWindow::InitializeDebugWidgets() { | |||
|             [this] { lleServiceModulesWidget->setDisabled(true); }); | ||||
|     connect(this, &GMainWindow::EmulationStopping, waitTreeWidget, | ||||
|             [this] { lleServiceModulesWidget->setDisabled(false); }); | ||||
| 
 | ||||
|     ipcRecorderWidget = new IPCRecorderWidget(this); | ||||
|     addDockWidget(Qt::RightDockWidgetArea, ipcRecorderWidget); | ||||
|     ipcRecorderWidget->hide(); | ||||
|     debug_menu->addAction(ipcRecorderWidget->toggleViewAction()); | ||||
|     connect(this, &GMainWindow::EmulationStarting, ipcRecorderWidget, | ||||
|             &IPCRecorderWidget::OnEmulationStarting); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::InitializeRecentFileMenuActions() { | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ class GraphicsBreakPointsWidget; | |||
| class GraphicsTracingWidget; | ||||
| class GraphicsVertexShaderWidget; | ||||
| class GRenderWindow; | ||||
| class IPCRecorderWidget; | ||||
| class LLEServiceModulesWidget; | ||||
| class MicroProfileDialog; | ||||
| class MultiplayerState; | ||||
|  | @ -254,6 +255,7 @@ private: | |||
|     GraphicsBreakPointsWidget* graphicsBreakpointsWidget; | ||||
|     GraphicsVertexShaderWidget* graphicsVertexShaderWidget; | ||||
|     GraphicsTracingWidget* graphicsTracingWidget; | ||||
|     IPCRecorderWidget* ipcRecorderWidget; | ||||
|     LLEServiceModulesWidget* lleServiceModulesWidget; | ||||
|     WaitTreeWidget* waitTreeWidget; | ||||
|     Updater* updater; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue