mirror of
https://github.com/starr-dusT/citra.git
synced 2024-10-02 10:26:17 -07:00
![zhupengfei](/assets/img/avatar_default.png)
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.
184 lines
6.3 KiB
C++
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();
|
|
}
|