citra/src/citra_qt/debugger/ipc/recorder.cpp
zhupengfei 42cefdbff0 citra_qt/debugger: Add recorder widget
This widget provides a simple list of recorded requests as well as a simple filter and the 'Clear' button. Requests with status 'HLE Unimplemented' or 'Error' will be marked out with the red color.

This widget handles retrival of service name. For reasons refer to a previous commit message.

Added the widget to the Debugging menu, pretty much the same as other debugging widgets.
2019-08-20 03:08:55 +08:00

184 lines
6.3 KiB
C++

// 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();
}