2019-07-22 12:49:39 +00:00
|
|
|
// Copyright 2019 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2020-07-23 00:41:10 +00:00
|
|
|
#include <QBrush>
|
2019-07-22 12:49:39 +00:00
|
|
|
#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"
|
|
|
|
|
2023-08-01 22:40:39 +00:00
|
|
|
IPCRecorderWidget::IPCRecorderWidget(Core::System& system_, QWidget* parent)
|
|
|
|
: QDockWidget(parent), ui(std::make_unique<Ui::IPCRecorder>()), system{system_} {
|
2019-07-22 12:49:39 +00:00
|
|
|
|
|
|
|
ui->setupUi(this);
|
|
|
|
qRegisterMetaType<IPCDebugger::RequestRecord>();
|
|
|
|
|
2023-08-01 22:40:39 +00:00
|
|
|
connect(ui->enabled, &QCheckBox::stateChanged, this,
|
2019-07-22 12:49:39 +00:00
|
|
|
[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();
|
2022-11-04 22:32:57 +00:00
|
|
|
return QLatin1String{};
|
2019-07-22 12:49:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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"));
|
|
|
|
}
|
|
|
|
|
2020-07-24 03:34:43 +00:00
|
|
|
QTreeWidgetItem entry{
|
2019-07-22 12:49:39 +00:00
|
|
|
{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;
|
2020-07-24 03:34:43 +00:00
|
|
|
(*ui->main->invisibleRootItem()->child(row_id)) = entry;
|
2019-07-22 12:49:39 +00:00
|
|
|
} else {
|
|
|
|
records.emplace_back(record);
|
2020-07-24 03:34:43 +00:00
|
|
|
ui->main->invisibleRootItem()->addChild(new QTreeWidgetItem(entry));
|
2019-07-22 12:49:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (record.status == IPCDebugger::RequestStatus::HLEUnimplemented ||
|
|
|
|
(record.status == IPCDebugger::RequestStatus::Handled &&
|
|
|
|
record.translated_reply_cmdbuf[1] != RESULT_SUCCESS.raw)) { // Unimplemented / Error
|
|
|
|
|
2023-08-01 22:40:39 +00:00
|
|
|
auto item = ui->main->invisibleRootItem()->child(row_id);
|
2019-07-22 12:49:39 +00:00
|
|
|
for (int column = 0; column < item->columnCount(); ++column) {
|
2020-07-23 00:41:10 +00:00
|
|
|
item->setBackground(column, QBrush(QColor::fromRgb(255, 0, 0)));
|
2019-07-22 12:49:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ApplyFilter(row_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
void IPCRecorderWidget::SetEnabled(bool enabled) {
|
2023-08-01 22:40:39 +00:00
|
|
|
if (!system.IsPoweredOn()) {
|
2019-07-22 12:49:39 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-08-01 22:40:39 +00:00
|
|
|
auto& ipc_recorder = system.Kernel().GetIPCRecorder();
|
2019-07-22 12:49:39 +00:00
|
|
|
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() {
|
2022-05-12 04:23:57 +00:00
|
|
|
id_offset += static_cast<int>(records.size());
|
2019-07-22 12:49:39 +00:00
|
|
|
|
|
|
|
records.clear();
|
|
|
|
ui->main->invisibleRootItem()->takeChildren();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString IPCRecorderWidget::GetServiceName(const IPCDebugger::RequestRecord& record) const {
|
2023-08-01 22:40:39 +00:00
|
|
|
if (system.IsPoweredOn() && record.client_port.id != -1) {
|
|
|
|
const Service::SM::ServiceManager& sm = system.ServiceManager();
|
|
|
|
const u32 port_id = static_cast<u32>(record.client_port.id);
|
|
|
|
const auto service_name = sm.GetServiceNameByPortId(port_id);
|
2019-07-22 12:49:39 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|