From 71e0c40310df741414a54111965903f8afd931cf Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:24:01 +0800
Subject: [PATCH 01/10] service: Add service function name lookup based on
 header code

This is for displaying the function name for HLE requests. Probably it is possible to do the same for LLE ones but it would require having the HLE handlers available even when not using them, which doesn't seem to make sense and is more of a hack than a proper solution in my opinion.
---
 src/core/hle/service/service.cpp | 8 ++++++++
 src/core/hle/service/service.h   | 3 +++
 2 files changed, 11 insertions(+)

diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 461d0930f..057562488 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -179,6 +179,14 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context)
     handler_invoker(this, info->handler_callback, context);
 }
 
+std::string ServiceFrameworkBase::GetFunctionName(u32 header) const {
+    if (!handlers.count(header)) {
+        return "";
+    }
+
+    return handlers.at(header).name;
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Module interface
 
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 1d9e800e2..db6a0ad23 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -64,6 +64,9 @@ public:
 
     void HandleSyncRequest(Kernel::HLERequestContext& context) override;
 
+    /// Retrieves name of a function based on the header code. For IPC Recorder.
+    std::string GetFunctionName(u32 header) const;
+
 protected:
     /// Member-function pointer type of SyncRequest handlers.
     template <typename Self>

From a3057c968bcf323d78f847b646e853d8d83e11b6 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:09:11 +0800
Subject: [PATCH 02/10] kernel: Add IPC Recorder class

This class resides in Kernel mainly because that, it's hard for kernel objects to get references to the System (and therefore to the Recorder), while much easier for KernelSystem. If this is to be moved to System the code will likely get much more complex with System (or Recorder) references passed everywhere.
---
 src/core/CMakeLists.txt                       |   2 +
 src/core/hle/kernel/ipc_debugger/recorder.cpp | 165 ++++++++++++++++++
 src/core/hle/kernel/ipc_debugger/recorder.h   | 129 ++++++++++++++
 3 files changed, 296 insertions(+)
 create mode 100644 src/core/hle/kernel/ipc_debugger/recorder.cpp
 create mode 100644 src/core/hle/kernel/ipc_debugger/recorder.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 0c7bbbecf..08554ce28 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -134,6 +134,8 @@ add_library(core STATIC
     hle/kernel/hle_ipc.h
     hle/kernel/ipc.cpp
     hle/kernel/ipc.h
+    hle/kernel/ipc_debugger/recorder.cpp
+    hle/kernel/ipc_debugger/recorder.h
     hle/kernel/kernel.cpp
     hle/kernel/kernel.h
     hle/kernel/memory.cpp
diff --git a/src/core/hle/kernel/ipc_debugger/recorder.cpp b/src/core/hle/kernel/ipc_debugger/recorder.cpp
new file mode 100644
index 000000000..968815c5b
--- /dev/null
+++ b/src/core/hle/kernel/ipc_debugger/recorder.cpp
@@ -0,0 +1,165 @@
+// Copyright 2019 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/ipc_debugger/recorder.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/server_port.h"
+#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
+#include "core/hle/kernel/thread.h"
+#include "core/hle/service/service.h"
+
+namespace IPCDebugger {
+
+namespace {
+ObjectInfo GetObjectInfo(const Kernel::Object* object) {
+    if (object == nullptr) {
+        return {};
+    }
+    return {object->GetTypeName(), object->GetName(), static_cast<int>(object->GetObjectId())};
+}
+
+ObjectInfo GetObjectInfo(const Kernel::Thread* thread) {
+    if (thread == nullptr) {
+        return {};
+    }
+    return {thread->GetTypeName(), thread->GetName(), static_cast<int>(thread->GetThreadId())};
+}
+
+ObjectInfo GetObjectInfo(const Kernel::Process* process) {
+    if (process == nullptr) {
+        return {};
+    }
+    return {process->GetTypeName(), process->GetName(), static_cast<int>(process->process_id)};
+}
+} // namespace
+
+Recorder::Recorder() = default;
+Recorder::~Recorder() = default;
+
+bool Recorder::IsEnabled() const {
+    return enabled.load(std::memory_order_relaxed);
+}
+
+void Recorder::RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
+                               const std::shared_ptr<Kernel::Thread>& client_thread) {
+    const u32 thread_id = client_thread->GetThreadId();
+
+    RequestRecord record = {/* id */ ++record_count,
+                            /* status */ RequestStatus::Sent,
+                            /* client_process */ GetObjectInfo(client_thread->owner_process),
+                            /* client_thread */ GetObjectInfo(client_thread.get()),
+                            /* client_session */ GetObjectInfo(client_session.get()),
+                            /* client_port */ GetObjectInfo(client_session->parent->port.get()),
+                            /* server_process */ {},
+                            /* server_thread */ {},
+                            /* server_session */ GetObjectInfo(client_session->parent->server)};
+    record_map.insert_or_assign(thread_id, std::make_unique<RequestRecord>(record));
+    client_session_map.insert_or_assign(thread_id, client_session);
+
+    InvokeCallbacks(record);
+}
+
+void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
+                              std::vector<u32> untranslated_cmdbuf,
+                              std::vector<u32> translated_cmdbuf,
+                              const std::shared_ptr<Kernel::Thread>& server_thread) {
+    const u32 thread_id = client_thread->GetThreadId();
+    if (!record_map.count(thread_id)) {
+        // This is possible when the recorder is enabled after application started
+        LOG_ERROR(Kernel, "No request is assoicated with the thread");
+        return;
+    }
+
+    auto& record = *record_map[thread_id];
+    record.status = RequestStatus::Handling;
+    record.untranslated_request_cmdbuf = std::move(untranslated_cmdbuf);
+    record.translated_request_cmdbuf = std::move(translated_cmdbuf);
+
+    if (server_thread) {
+        record.server_process = GetObjectInfo(server_thread->owner_process);
+        record.server_thread = GetObjectInfo(server_thread.get());
+    } else {
+        record.is_hle = true;
+    }
+
+    // Function name
+    ASSERT_MSG(client_session_map.count(thread_id), "Client session is missing");
+    const auto& client_session = client_session_map[thread_id];
+    if (client_session->parent->port &&
+        client_session->parent->port->GetServerPort()->hle_handler) {
+
+        record.function_name = std::dynamic_pointer_cast<Service::ServiceFrameworkBase>(
+                                   client_session->parent->port->GetServerPort()->hle_handler)
+                                   ->GetFunctionName(record.untranslated_request_cmdbuf[0]);
+    }
+    client_session_map.erase(thread_id);
+
+    InvokeCallbacks(record);
+}
+
+void Recorder::SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
+                            std::vector<u32> untranslated_cmdbuf,
+                            std::vector<u32> translated_cmdbuf) {
+    const u32 thread_id = client_thread->GetThreadId();
+    if (!record_map.count(thread_id)) {
+        // This is possible when the recorder is enabled after application started
+        LOG_ERROR(Kernel, "No request is assoicated with the thread");
+        return;
+    }
+
+    auto& record = *record_map[thread_id];
+    if (record.status != RequestStatus::HLEUnimplemented) {
+        record.status = RequestStatus::Handled;
+    }
+
+    record.untranslated_reply_cmdbuf = std::move(untranslated_cmdbuf);
+    record.translated_reply_cmdbuf = std::move(translated_cmdbuf);
+    InvokeCallbacks(record);
+
+    record_map.erase(thread_id);
+}
+
+void Recorder::SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread) {
+    const u32 thread_id = client_thread->GetThreadId();
+    if (!record_map.count(thread_id)) {
+        // This is possible when the recorder is enabled after application started
+        LOG_ERROR(Kernel, "No request is assoicated with the thread");
+        return;
+    }
+
+    auto& record = *record_map[thread_id];
+    record.status = RequestStatus::HLEUnimplemented;
+}
+
+CallbackHandle Recorder::BindCallback(CallbackType callback) {
+    std::unique_lock lock(callback_mutex);
+    CallbackHandle handle = std::make_shared<CallbackType>(callback);
+    callbacks.emplace(handle);
+    return handle;
+}
+
+void Recorder::UnbindCallback(const CallbackHandle& handle) {
+    std::unique_lock lock(callback_mutex);
+    callbacks.erase(handle);
+}
+
+void Recorder::InvokeCallbacks(const RequestRecord& request) {
+    {
+        std::shared_lock lock(callback_mutex);
+        for (const auto& iter : callbacks) {
+            (*iter)(request);
+        }
+    }
+}
+
+void Recorder::SetEnabled(bool enabled_) {
+    enabled.store(enabled_, std::memory_order_relaxed);
+}
+
+} // namespace IPCDebugger
diff --git a/src/core/hle/kernel/ipc_debugger/recorder.h b/src/core/hle/kernel/ipc_debugger/recorder.h
new file mode 100644
index 000000000..ebd8bf5d1
--- /dev/null
+++ b/src/core/hle/kernel/ipc_debugger/recorder.h
@@ -0,0 +1,129 @@
+// Copyright 2019 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <set>
+#include <shared_mutex>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Kernel {
+class ClientSession;
+class Thread;
+} // namespace Kernel
+
+namespace IPCDebugger {
+
+/**
+ * Record of a kernel object, for debugging purposes.
+ */
+struct ObjectInfo {
+    std::string type;
+    std::string name;
+    int id = -1;
+};
+
+/**
+ * Status of a request.
+ */
+enum class RequestStatus {
+    Invalid,          ///< Invalid status
+    Sent,             ///< The request is sent to the kernel and is waiting to be handled
+    Handling,         ///< The request is being handled
+    Handled,          ///< The request is handled with reply sent
+    HLEUnimplemented, ///< The request is unimplemented by HLE, and unhandled
+};
+
+/**
+ * Record of an IPC request.
+ */
+struct RequestRecord {
+    int id;
+    RequestStatus status = RequestStatus::Invalid;
+    ObjectInfo client_process;
+    ObjectInfo client_thread;
+    ObjectInfo client_session;
+    ObjectInfo client_port;    // Not available for portless
+    ObjectInfo server_process; // Only available for LLE requests
+    ObjectInfo server_thread;  // Only available for LLE requests
+    ObjectInfo server_session;
+    std::string function_name; // Not available for LLE or portless
+    bool is_hle = false;
+    // Request info is only available when status is not `Invalid` or `Sent`
+    std::vector<u32> untranslated_request_cmdbuf;
+    std::vector<u32> translated_request_cmdbuf;
+    // Reply info is only available when status is `Handled`
+    std::vector<u32> untranslated_reply_cmdbuf;
+    std::vector<u32> translated_reply_cmdbuf;
+};
+
+using CallbackType = std::function<void(const RequestRecord&)>;
+using CallbackHandle = std::shared_ptr<CallbackType>;
+
+class Recorder {
+public:
+    explicit Recorder();
+    ~Recorder();
+
+    /**
+     * Returns whether the recorder is enabled.
+     */
+    bool IsEnabled() const;
+
+    /**
+     * Registers a request into the recorder. The request is then assoicated with the client thread.
+     */
+    void RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
+                         const std::shared_ptr<Kernel::Thread>& client_thread);
+
+    /**
+     * Sets the request information of the request record associated with the client thread.
+     * When the server thread is empty, the request will be considered HLE.
+     */
+    void SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
+                        std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf,
+                        const std::shared_ptr<Kernel::Thread>& server_thread = {});
+
+    /**
+     * Sets the reply information of the request record assoicated with the client thread.
+     * The request is then unlinked from the client thread.
+     */
+    void SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
+                      std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf);
+
+    /**
+     * Set the status of a record to HLEUnimplemented.
+     */
+    void SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread);
+
+    /**
+     * Set the status of the debugger (enabled/disabled).
+     */
+    void SetEnabled(bool enabled);
+
+    CallbackHandle BindCallback(CallbackType callback);
+    void UnbindCallback(const CallbackHandle& handle);
+
+private:
+    void InvokeCallbacks(const RequestRecord& request);
+
+    std::unordered_map<u32, std::unique_ptr<RequestRecord>> record_map;
+    int record_count{};
+
+    // Temporary client session map for function name handling
+    std::unordered_map<u32, std::shared_ptr<Kernel::ClientSession>> client_session_map;
+
+    std::atomic_bool enabled{false};
+
+    std::set<CallbackHandle> callbacks;
+    mutable std::shared_mutex callback_mutex;
+};
+
+} // namespace IPCDebugger

From b093d39a273d070ba763e32065d14bc637f4de1c Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:10:47 +0800
Subject: [PATCH 03/10] kernel: Add IPC Recorder to KernelSystem

Refer to the previous commit message for reasons why this is in kernel.
---
 src/core/hle/kernel/kernel.cpp | 10 ++++++++++
 src/core/hle/kernel/kernel.h   |  9 +++++++++
 2 files changed, 19 insertions(+)

diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index f480f6362..ceb2f14f5 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -5,6 +5,7 @@
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/config_mem.h"
 #include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/ipc_debugger/recorder.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/process.h"
@@ -25,6 +26,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
     resource_limits = std::make_unique<ResourceLimitList>(*this);
     thread_manager = std::make_unique<ThreadManager>(*this);
     timer_manager = std::make_unique<TimerManager>(timing);
+    ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
 }
 
 /// Shutdown the kernel
@@ -87,6 +89,14 @@ const SharedPage::Handler& KernelSystem::GetSharedPageHandler() const {
     return *shared_page_handler;
 }
 
+IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() {
+    return *ipc_recorder;
+}
+
+const IPCDebugger::Recorder& KernelSystem::GetIPCRecorder() const {
+    return *ipc_recorder;
+}
+
 void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
     named_ports.emplace(std::move(name), std::move(port));
 }
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 38045a425..58f63938b 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -32,6 +32,10 @@ namespace Core {
 class Timing;
 }
 
+namespace IPCDebugger {
+class Recorder;
+}
+
 namespace Kernel {
 
 class AddressArbiter;
@@ -222,6 +226,9 @@ public:
     SharedPage::Handler& GetSharedPageHandler();
     const SharedPage::Handler& GetSharedPageHandler() const;
 
+    IPCDebugger::Recorder& GetIPCRecorder();
+    const IPCDebugger::Recorder& GetIPCRecorder() const;
+
     MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
 
     void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
@@ -274,6 +281,8 @@ private:
 
     std::unique_ptr<ConfigMem::Handler> config_mem_handler;
     std::unique_ptr<SharedPage::Handler> shared_page_handler;
+
+    std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
 };
 
 } // namespace Kernel

From cb0bd6530c3d7dbb83ba18d970f962cb5ca79434 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:17:29 +0800
Subject: [PATCH 04/10] kernel/svc: Add request registering

All necessary objects are available here, making this a great place for the registering part.
---
 src/core/hle/kernel/svc.cpp | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 88a0ff5ba..ef0f35237 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -387,7 +387,13 @@ ResultCode SVC::SendSyncRequest(Handle handle) {
 
     system.PrepareReschedule();
 
-    return session->SendSyncRequest(SharedFrom(kernel.GetThreadManager().GetCurrentThread()));
+    auto thread = SharedFrom(kernel.GetThreadManager().GetCurrentThread());
+
+    if (kernel.GetIPCRecorder().IsEnabled()) {
+        kernel.GetIPCRecorder().RegisterRequest(session, thread);
+    }
+
+    return session->SendSyncRequest(thread);
 }
 
 /// Close a handle

From a27dfc269a4d51d05089a4c8776db81bf4fadf39 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:19:03 +0800
Subject: [PATCH 05/10] kernel: Add LLE request/reply recording

The 'translate' function is a great place to put this in IMO as it is possible to get both untranslated and translated cmdbufs. However a kernel reference has to be passed here, but it is not too hard fortunately.
---
 src/core/hle/kernel/ipc.cpp | 22 +++++++++++++++++++++-
 src/core/hle/kernel/ipc.h   |  5 ++++-
 src/core/hle/kernel/svc.cpp | 25 +++++++++++++------------
 3 files changed, 38 insertions(+), 14 deletions(-)

diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp
index c9616e566..3dacb9831 100644
--- a/src/core/hle/kernel/ipc.cpp
+++ b/src/core/hle/kernel/ipc.cpp
@@ -8,6 +8,7 @@
 #include "core/hle/ipc.h"
 #include "core/hle/kernel/handle_table.h"
 #include "core/hle/kernel/ipc.h"
+#include "core/hle/kernel/ipc_debugger/recorder.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/process.h"
@@ -16,7 +17,8 @@
 
 namespace Kernel {
 
-ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<Thread> src_thread,
+ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
+                                  std::shared_ptr<Thread> src_thread,
                                   std::shared_ptr<Thread> dst_thread, VAddr src_address,
                                   VAddr dst_address,
                                   std::vector<MappedBufferContext>& mapped_buffer_context,
@@ -37,6 +39,13 @@ ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<
     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
     memory.ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32));
 
+    const bool should_record = kernel.GetIPCRecorder().IsEnabled();
+
+    std::vector<u32> untranslated_cmdbuf;
+    if (should_record) {
+        untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
+    }
+
     std::size_t i = untranslated_size;
     while (i < command_size) {
         u32 descriptor = cmd_buf[i];
@@ -218,6 +227,17 @@ ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<
         }
     }
 
+    if (should_record) {
+        std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
+        if (reply) {
+            kernel.GetIPCRecorder().SetReplyInfo(dst_thread, std::move(untranslated_cmdbuf),
+                                                 std::move(translated_cmdbuf));
+        } else {
+            kernel.GetIPCRecorder().SetRequestInfo(src_thread, std::move(untranslated_cmdbuf),
+                                                   std::move(translated_cmdbuf), dst_thread);
+        }
+    }
+
     memory.WriteBlock(*dst_process, dst_address, cmd_buf.data(), command_size * sizeof(u32));
 
     return RESULT_SUCCESS;
diff --git a/src/core/hle/kernel/ipc.h b/src/core/hle/kernel/ipc.h
index d84028767..b06079958 100644
--- a/src/core/hle/kernel/ipc.h
+++ b/src/core/hle/kernel/ipc.h
@@ -16,6 +16,8 @@ class MemorySystem;
 
 namespace Kernel {
 
+class KernelSystem;
+
 struct MappedBufferContext {
     IPC::MappedBufferPermissions permissions;
     u32 size;
@@ -27,7 +29,8 @@ struct MappedBufferContext {
 };
 
 /// Performs IPC command buffer translation from one process to another.
-ResultCode TranslateCommandBuffer(Memory::MemorySystem& memory, std::shared_ptr<Thread> src_thread,
+ResultCode TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory,
+                                  std::shared_ptr<Thread> src_thread,
                                   std::shared_ptr<Thread> dst_thread, VAddr src_address,
                                   VAddr dst_address,
                                   std::vector<MappedBufferContext>& mapped_buffer_context,
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index ef0f35237..b5ebaf936 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -19,6 +19,7 @@
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/handle_table.h"
 #include "core/hle/kernel/ipc.h"
+#include "core/hle/kernel/ipc_debugger/recorder.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/kernel/process.h"
@@ -599,7 +600,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
     }
 }
 
-static ResultCode ReceiveIPCRequest(Memory::MemorySystem& memory,
+static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
                                     std::shared_ptr<ServerSession> server_session,
                                     std::shared_ptr<Thread> thread) {
     if (server_session->parent->client == nullptr) {
@@ -609,9 +610,9 @@ static ResultCode ReceiveIPCRequest(Memory::MemorySystem& memory,
     VAddr target_address = thread->GetCommandBufferAddress();
     VAddr source_address = server_session->currently_handling->GetCommandBufferAddress();
 
-    ResultCode translation_result =
-        TranslateCommandBuffer(memory, server_session->currently_handling, thread, source_address,
-                               target_address, server_session->mapped_buffer_context, false);
+    ResultCode translation_result = TranslateCommandBuffer(
+        kernel, memory, server_session->currently_handling, thread, source_address, target_address,
+        server_session->mapped_buffer_context, false);
 
     // If a translation error occurred, immediately resume the client thread.
     if (translation_result.IsError()) {
@@ -676,9 +677,9 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
         VAddr source_address = thread->GetCommandBufferAddress();
         VAddr target_address = request_thread->GetCommandBufferAddress();
 
-        ResultCode translation_result =
-            TranslateCommandBuffer(memory, SharedFrom(thread), request_thread, source_address,
-                                   target_address, session->mapped_buffer_context, true);
+        ResultCode translation_result = TranslateCommandBuffer(
+            kernel, memory, SharedFrom(thread), request_thread, source_address, target_address,
+            session->mapped_buffer_context, true);
 
         // Note: The real kernel seems to always panic if the Server->Client buffer translation
         // fails for whatever reason.
@@ -713,7 +714,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
             return RESULT_SUCCESS;
 
         auto server_session = static_cast<ServerSession*>(object);
-        return ReceiveIPCRequest(memory, SharedFrom(server_session), SharedFrom(thread));
+        return ReceiveIPCRequest(kernel, memory, SharedFrom(server_session), SharedFrom(thread));
     }
 
     // No objects were ready to be acquired, prepare to suspend the thread.
@@ -729,9 +730,9 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
 
     thread->wait_objects = std::move(objects);
 
-    thread->wakeup_callback = [& memory = this->memory](ThreadWakeupReason reason,
-                                                        std::shared_ptr<Thread> thread,
-                                                        std::shared_ptr<WaitObject> object) {
+    thread->wakeup_callback = [& kernel = this->kernel, &memory = this->memory](
+                                  ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                                  std::shared_ptr<WaitObject> object) {
         ASSERT(thread->status == ThreadStatus::WaitSynchAny);
         ASSERT(reason == ThreadWakeupReason::Signal);
 
@@ -739,7 +740,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
 
         if (object->GetHandleType() == HandleType::ServerSession) {
             auto server_session = DynamicObjectCast<ServerSession>(object);
-            result = ReceiveIPCRequest(memory, server_session, thread);
+            result = ReceiveIPCRequest(kernel, memory, server_session, thread);
         }
 
         thread->SetWaitSynchronizationResult(result);

From efd69e13158e10700925b203e0d054c2aee9118d Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:25:43 +0800
Subject: [PATCH 06/10] kernel, service: Add HLE request/reply recording

Pretty much the same as LLE requests, the 'translate' part is chosen. A function is added to the context class to record requests that involves unimplemented HLE functions.
---
 src/core/hle/kernel/hle_ipc.cpp  | 33 ++++++++++++++++++++++++++++++++
 src/core/hle/kernel/hle_ipc.h    |  3 +++
 src/core/hle/service/service.cpp |  1 +
 3 files changed, 37 insertions(+)

diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index cd2f9fc87..ab4ecfd05 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -10,6 +10,7 @@
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/handle_table.h"
 #include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/ipc_debugger/recorder.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/process.h"
 
@@ -107,6 +108,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
 
     std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin());
 
+    const bool should_record = kernel.GetIPCRecorder().IsEnabled();
+
+    std::vector<u32> untranslated_cmdbuf;
+    if (should_record) {
+        untranslated_cmdbuf = std::vector<u32>{src_cmdbuf, src_cmdbuf + command_size};
+    }
+
     std::size_t i = untranslated_size;
     while (i < command_size) {
         u32 descriptor = cmd_buf[i] = src_cmdbuf[i];
@@ -160,6 +168,12 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
         }
     }
 
+    if (should_record) {
+        std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
+        kernel.GetIPCRecorder().SetRequestInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
+                                               std::move(translated_cmdbuf));
+    }
+
     return RESULT_SUCCESS;
 }
 
@@ -173,6 +187,13 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
 
     std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf);
 
+    const bool should_record = kernel.GetIPCRecorder().IsEnabled();
+
+    std::vector<u32> untranslated_cmdbuf;
+    if (should_record) {
+        untranslated_cmdbuf = std::vector<u32>{cmd_buf.begin(), cmd_buf.begin() + command_size};
+    }
+
     std::size_t i = untranslated_size;
     while (i < command_size) {
         u32 descriptor = dst_cmdbuf[i] = cmd_buf[i];
@@ -225,6 +246,12 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
         }
     }
 
+    if (should_record) {
+        std::vector<u32> translated_cmdbuf{dst_cmdbuf, dst_cmdbuf + command_size};
+        kernel.GetIPCRecorder().SetReplyInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
+                                             std::move(translated_cmdbuf));
+    }
+
     return RESULT_SUCCESS;
 }
 
@@ -233,6 +260,12 @@ MappedBuffer& HLERequestContext::GetMappedBuffer(u32 id_from_cmdbuf) {
     return request_mapped_buffers[id_from_cmdbuf];
 }
 
+void HLERequestContext::ReportUnimplemented() const {
+    if (kernel.GetIPCRecorder().IsEnabled()) {
+        kernel.GetIPCRecorder().SetHLEUnimplemented(SharedFrom(thread));
+    }
+}
+
 MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor,
                            VAddr address, u32 id)
     : memory(&memory), id(id), address(address), process(&process) {
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index f684985d0..26942fe6b 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -234,6 +234,9 @@ public:
     /// Writes data from this context back to the requesting process/thread.
     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
 
+    /// Reports an unimplemented function.
+    void ReportUnimplemented() const;
+
 private:
     KernelSystem& kernel;
     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 057562488..0310d4ad9 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -171,6 +171,7 @@ void ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context)
     auto itr = handlers.find(header_code);
     const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
     if (info == nullptr || info->handler_callback == nullptr) {
+        context.ReportUnimplemented();
         return ReportUnimplementedFunction(context.CommandBuffer(), info);
     }
 

From f40232adc97da95456b63c051e9cb75a9e001322 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:27:35 +0800
Subject: [PATCH 07/10] service/sm: Add service name retrival based on client
 port ID

This is for displaying the service names. This function is only used in the frontend, because Recorder which is in the Kernel cannot and should not have access to SM in the System.
---
 src/core/hle/service/sm/sm.cpp | 9 +++++++++
 src/core/hle/service/sm/sm.h   | 6 ++++++
 2 files changed, 15 insertions(+)

diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index e37c72cc1..3caca2f93 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -42,6 +42,7 @@ ResultVal<std::shared_ptr<Kernel::ServerPort>> ServiceManager::RegisterService(
 
     auto [server_port, client_port] = system.Kernel().CreatePortPair(max_sessions, name);
 
+    registered_services_inverse.emplace(client_port->GetObjectId(), name);
     registered_services.emplace(std::move(name), std::move(client_port));
     return MakeResult(std::move(server_port));
 }
@@ -65,4 +66,12 @@ ResultVal<std::shared_ptr<Kernel::ClientSession>> ServiceManager::ConnectToServi
     return client_port->Connect();
 }
 
+std::string ServiceManager::GetServiceNameByPortId(u32 port) const {
+    if (registered_services_inverse.count(port)) {
+        return registered_services_inverse.at(port);
+    }
+
+    return "";
+}
+
 } // namespace Service::SM
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index dc26d8814..6e47fd152 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -51,6 +51,8 @@ public:
                                                                    unsigned int max_sessions);
     ResultVal<std::shared_ptr<Kernel::ClientPort>> GetServicePort(const std::string& name);
     ResultVal<std::shared_ptr<Kernel::ClientSession>> ConnectToService(const std::string& name);
+    // For IPC Recorder
+    std::string GetServiceNameByPortId(u32 port) const;
 
     template <typename T>
     std::shared_ptr<T> GetService(const std::string& service_name) const {
@@ -74,6 +76,10 @@ private:
 
     /// Map of registered services, retrieved using GetServicePort or ConnectToService.
     std::unordered_map<std::string, std::shared_ptr<Kernel::ClientPort>> registered_services;
+
+    // For IPC Recorder
+    /// client port Object id -> service name
+    std::unordered_map<u32, std::string> registered_services_inverse;
 };
 
 } // namespace Service::SM

From 45930e0621f92e949e8083eb1967e6cae62290d0 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:47:47 +0800
Subject: [PATCH 08/10] citra_qt: Add record dialog

This 'View Record' dialog shows all relevant information on a record as well as the command buffers.
---
 src/citra_qt/CMakeLists.txt                 |   3 +
 src/citra_qt/debugger/ipc/record_dialog.cpp |  64 +++++
 src/citra_qt/debugger/ipc/record_dialog.h   |  36 +++
 src/citra_qt/debugger/ipc/record_dialog.ui  | 281 ++++++++++++++++++++
 4 files changed, 384 insertions(+)
 create mode 100644 src/citra_qt/debugger/ipc/record_dialog.cpp
 create mode 100644 src/citra_qt/debugger/ipc/record_dialog.h
 create mode 100644 src/citra_qt/debugger/ipc/record_dialog.ui

diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 6ac1eb2db..9d5137a4f 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -90,6 +90,9 @@ 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/lle_service_modules.cpp
     debugger/lle_service_modules.h
     debugger/profiler.cpp
diff --git a/src/citra_qt/debugger/ipc/record_dialog.cpp b/src/citra_qt/debugger/ipc/record_dialog.cpp
new file mode 100644
index 000000000..2a179e10f
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/record_dialog.cpp
@@ -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]));
+}
diff --git a/src/citra_qt/debugger/ipc/record_dialog.h b/src/citra_qt/debugger/ipc/record_dialog.h
new file mode 100644
index 000000000..40618b925
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/record_dialog.h
@@ -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;
+};
diff --git a/src/citra_qt/debugger/ipc/record_dialog.ui b/src/citra_qt/debugger/ipc/record_dialog.ui
new file mode 100644
index 000000000..44c70d386
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/record_dialog.ui
@@ -0,0 +1,281 @@
+<?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="QVBoxLayout">
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Process:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="clientProcess">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Thread:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="clientThread">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Session:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="clientSession">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox">
+       <property name="title">
+        <string>Server</string>
+       </property>
+       <layout class="QVBoxLayout">
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Process:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="serverProcess">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Thread:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="serverThread">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QHBoxLayout">
+          <item>
+           <widget class="QLabel">
+            <property name="text">
+             <string>Session:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="serverSession">
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QGroupBox">
+     <property name="title">
+      <string>General</string>
+     </property>
+     <layout class="QVBoxLayout">
+      <item>
+       <layout class="QHBoxLayout">
+        <item>
+         <widget class="QLabel">
+          <property name="text">
+           <string>Client Port:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="clientPort">
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout">
+        <item>
+         <widget class="QLabel">
+          <property name="text">
+           <string>Service:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="service">
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout">
+        <item>
+         <widget class="QLabel">
+          <property name="text">
+           <string>Function:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="function">
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </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>

From 42cefdbff0dd1127a008aa29f9ab4eaaf437012d Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Mon, 22 Jul 2019 20:49:39 +0800
Subject: [PATCH 09/10] 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.
---
 src/citra_qt/CMakeLists.txt            |   3 +
 src/citra_qt/debugger/ipc/recorder.cpp | 183 +++++++++++++++++++++++++
 src/citra_qt/debugger/ipc/recorder.h   |  52 +++++++
 src/citra_qt/debugger/ipc/recorder.ui  |  93 +++++++++++++
 src/citra_qt/main.cpp                  |   8 ++
 src/citra_qt/main.h                    |   2 +
 6 files changed, 341 insertions(+)
 create mode 100644 src/citra_qt/debugger/ipc/recorder.cpp
 create mode 100644 src/citra_qt/debugger/ipc/recorder.h
 create mode 100644 src/citra_qt/debugger/ipc/recorder.ui

diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 9d5137a4f..0198ada76 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -93,6 +93,9 @@ add_executable(citra-qt
     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
diff --git a/src/citra_qt/debugger/ipc/recorder.cpp b/src/citra_qt/debugger/ipc/recorder.cpp
new file mode 100644
index 000000000..24128e38a
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/recorder.cpp
@@ -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();
+}
diff --git a/src/citra_qt/debugger/ipc/recorder.h b/src/citra_qt/debugger/ipc/recorder.h
new file mode 100644
index 000000000..63b190790
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/recorder.h
@@ -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);
diff --git a/src/citra_qt/debugger/ipc/recorder.ui b/src/citra_qt/debugger/ipc/recorder.ui
new file mode 100644
index 000000000..78b988fdc
--- /dev/null
+++ b/src/citra_qt/debugger/ipc/recorder.ui
@@ -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>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index d1a31fa5c..49e4588ae 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -36,6 +36,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"
@@ -328,6 +329,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() {
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 780589141..c5861cb2e 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -30,6 +30,7 @@ class GraphicsBreakPointsWidget;
 class GraphicsTracingWidget;
 class GraphicsVertexShaderWidget;
 class GRenderWindow;
+class IPCRecorderWidget;
 class LLEServiceModulesWidget;
 class MicroProfileDialog;
 class MultiplayerState;
@@ -247,6 +248,7 @@ private:
     GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
     GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
     GraphicsTracingWidget* graphicsTracingWidget;
+    IPCRecorderWidget* ipcRecorderWidget;
     LLEServiceModulesWidget* lleServiceModulesWidget;
     WaitTreeWidget* waitTreeWidget;
     Updater* updater;

From 3d14abeb4118974128c25539711ad1f86dade6be Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Thu, 29 Aug 2019 00:03:56 +0800
Subject: [PATCH 10/10] citra_qt/record_dialog: Switch to QFormLayout

---
 src/citra_qt/debugger/ipc/record_dialog.ui | 276 +++++++++------------
 1 file changed, 120 insertions(+), 156 deletions(-)

diff --git a/src/citra_qt/debugger/ipc/record_dialog.ui b/src/citra_qt/debugger/ipc/record_dialog.ui
index 44c70d386..0f5c7a435 100644
--- a/src/citra_qt/debugger/ipc/record_dialog.ui
+++ b/src/citra_qt/debugger/ipc/record_dialog.ui
@@ -21,60 +21,48 @@
        <property name="title">
         <string>Client</string>
        </property>
-       <layout class="QVBoxLayout">
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Process:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="clientProcess">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+       <layout class="QFormLayout">
+        <item row="0" column="0">
+         <widget class="QLabel">
+          <property name="text">
+           <string>Process:</string>
+          </property>
+         </widget>
         </item>
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Thread:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="clientThread">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+        <item row="0" column="1">
+         <widget class="QLineEdit" name="clientProcess">
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
         </item>
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Session:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="clientSession">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+        <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>
@@ -84,60 +72,48 @@
        <property name="title">
         <string>Server</string>
        </property>
-       <layout class="QVBoxLayout">
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Process:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="serverProcess">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+       <layout class="QFormLayout">
+        <item row="0" column="0">
+         <widget class="QLabel">
+          <property name="text">
+           <string>Process:</string>
+          </property>
+         </widget>
         </item>
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Thread:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="serverThread">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+        <item row="0" column="1">
+         <widget class="QLineEdit" name="serverProcess">
+          <property name="readOnly">
+           <bool>true</bool>
+          </property>
+         </widget>
         </item>
-        <item>
-         <layout class="QHBoxLayout">
-          <item>
-           <widget class="QLabel">
-            <property name="text">
-             <string>Session:</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QLineEdit" name="serverSession">
-            <property name="readOnly">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-         </layout>
+        <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>
@@ -149,60 +125,48 @@
      <property name="title">
       <string>General</string>
      </property>
-     <layout class="QVBoxLayout">
-      <item>
-       <layout class="QHBoxLayout">
-        <item>
-         <widget class="QLabel">
-          <property name="text">
-           <string>Client Port:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLineEdit" name="clientPort">
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-       </layout>
+     <layout class="QFormLayout">
+      <item row="0" column="0">
+       <widget class="QLabel">
+        <property name="text">
+         <string>Client Port:</string>
+        </property>
+       </widget>
       </item>
-      <item>
-       <layout class="QHBoxLayout">
-        <item>
-         <widget class="QLabel">
-          <property name="text">
-           <string>Service:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLineEdit" name="service">
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-       </layout>
+      <item row="0" column="1">
+       <widget class="QLineEdit" name="clientPort">
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+       </widget>
       </item>
-      <item>
-       <layout class="QHBoxLayout">
-        <item>
-         <widget class="QLabel">
-          <property name="text">
-           <string>Function:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLineEdit" name="function">
-          <property name="readOnly">
-           <bool>true</bool>
-          </property>
-         </widget>
-        </item>
-       </layout>
+      <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>