From 6bcd676b610773727f446a8c81bcca1c64a95b10 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Tue, 11 Oct 2022 18:15:30 -0400
Subject: [PATCH 1/3] general: preliminary support for hbl

---
 src/core/hle/service/ns/ns.cpp       | 30 ++++++++++-
 src/core/hle/service/ns/ns.h         |  3 ++
 src/core/hle/service/ptm/ts.cpp      | 15 ++++--
 src/core/hle/service/ptm/ts.h        |  1 +
 src/core/hle/service/set/set_sys.cpp | 79 +++++++++++++++++++++++++++-
 src/core/hle/service/set/set_sys.h   |  2 +
 6 files changed, 124 insertions(+), 6 deletions(-)

diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index f7318c3cb8..f59a1a63d0 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -8,6 +8,7 @@
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/vfs.h"
 #include "core/hle/ipc_helpers.h"
+#include "core/hle/service/glue/glue_manager.h"
 #include "core/hle/service/ns/errors.h"
 #include "core/hle/service/ns/iplatform_service_manager.h"
 #include "core/hle/service/ns/language.h"
@@ -581,7 +582,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
     : ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
     // clang-format off
     static const FunctionInfo functions[] = {
-        {0, nullptr, "GetApplicationControlData"},
+        {0, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData, "GetApplicationControlData"},
         {1, nullptr, "GetApplicationDesiredLanguage"},
         {2, nullptr, "ConvertApplicationLanguageToLanguageCode"},
         {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
@@ -594,6 +595,33 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
 
 IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default;
 
+void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(
+    Kernel::HLERequestContext& ctx) {
+    enum class ApplicationControlSource : u8 {
+        CacheOnly,
+        Storage,
+        StorageOnly,
+    };
+
+    struct RequestParameters {
+        ApplicationControlSource source;
+        u64 application_id;
+    };
+    static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size.");
+
+    IPC::RequestParser rp{ctx};
+    const auto parameters{rp.PopRaw<RequestParameters>()};
+    const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)};
+    const auto result = nacp_data ? ResultSuccess : ResultUnknown;
+
+    if (nacp_data) {
+        ctx.WriteBuffer(nacp_data->data(), nacp_data->size());
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(result);
+}
+
 NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} {
     // clang-format off
     static const FunctionInfo functions[] = {
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 4dc1915180..9c18e935c5 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -78,6 +78,9 @@ class IReadOnlyApplicationControlDataInterface final
 public:
     explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
     ~IReadOnlyApplicationControlDataInterface() override;
+
+private:
+    void GetApplicationControlData(Kernel::HLERequestContext& ctx);
 };
 
 class NS final : public ServiceFramework<NS> {
diff --git a/src/core/hle/service/ptm/ts.cpp b/src/core/hle/service/ptm/ts.cpp
index 65c3f135f4..b1a0a5544a 100644
--- a/src/core/hle/service/ptm/ts.cpp
+++ b/src/core/hle/service/ptm/ts.cpp
@@ -15,7 +15,7 @@ TS::TS(Core::System& system_) : ServiceFramework{system_, "ts"} {
         {0, nullptr, "GetTemperatureRange"},
         {1, &TS::GetTemperature, "GetTemperature"},
         {2, nullptr, "SetMeasurementMode"},
-        {3, nullptr, "GetTemperatureMilliC"},
+        {3, &TS::GetTemperatureMilliC, "GetTemperatureMilliC"},
         {4, nullptr, "OpenSession"},
     };
     // clang-format on
@@ -29,8 +29,6 @@ void TS::GetTemperature(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx};
     const auto location{rp.PopEnum<Location>()};
 
-    LOG_WARNING(Service_HID, "(STUBBED) called. location={}", location);
-
     const s32 temperature = location == Location::Internal ? 35 : 20;
 
     IPC::ResponseBuilder rb{ctx, 3};
@@ -38,4 +36,15 @@ void TS::GetTemperature(Kernel::HLERequestContext& ctx) {
     rb.Push(temperature);
 }
 
+void TS::GetTemperatureMilliC(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto location{rp.PopEnum<Location>()};
+
+    const s32 temperature = location == Location::Internal ? 35000 : 20000;
+
+    IPC::ResponseBuilder rb{ctx, 3};
+    rb.Push(ResultSuccess);
+    rb.Push(temperature);
+}
+
 } // namespace Service::PTM
diff --git a/src/core/hle/service/ptm/ts.h b/src/core/hle/service/ptm/ts.h
index 39a734ef7a..39d51847ee 100644
--- a/src/core/hle/service/ptm/ts.h
+++ b/src/core/hle/service/ptm/ts.h
@@ -20,6 +20,7 @@ private:
     };
 
     void GetTemperature(Kernel::HLERequestContext& ctx);
+    void GetTemperatureMilliC(Kernel::HLERequestContext& ctx);
 };
 
 } // namespace Service::PTM
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index 2a0b812c1e..d7cea6aac8 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -101,6 +101,81 @@ void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) {
     rb.Push(ResultSuccess);
 }
 
+// FIXME: implement support for the real system_settings.ini
+
+template <typename T>
+static std::vector<u8> ToBytes(const T& value) {
+    static_assert(std::is_trivially_copyable_v<T>);
+
+    const auto* begin = reinterpret_cast<const u8*>(&value);
+    const auto* end = begin + sizeof(T);
+
+    return std::vector<u8>(begin, end);
+}
+
+using Settings =
+    std::map<std::string, std::map<std::string, std::vector<u8>, std::less<>>, std::less<>>;
+
+static Settings GetSettings() {
+    Settings ret;
+
+    ret["hbloader"]["applet_heap_size"] = ToBytes(u64{0x0});
+    ret["hbloader"]["applet_heap_reservation_size"] = ToBytes(u64{0x8600000});
+
+    return ret;
+}
+
+void SET_SYS::GetSettingsItemValueSize(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_SET, "called");
+
+    // The category of the setting. This corresponds to the top-level keys of
+    // system_settings.ini.
+    const auto setting_category_buf{ctx.ReadBuffer(0)};
+    const std::string setting_category{setting_category_buf.begin(), setting_category_buf.end()};
+
+    // The name of the setting. This corresponds to the second-level keys of
+    // system_settings.ini.
+    const auto setting_name_buf{ctx.ReadBuffer(1)};
+    const std::string setting_name{setting_name_buf.begin(), setting_name_buf.end()};
+
+    auto settings{GetSettings()};
+    u64 response_size{0};
+
+    if (settings.contains(setting_category) && settings[setting_category].contains(setting_name)) {
+        response_size = settings[setting_category][setting_name].size();
+    }
+
+    IPC::ResponseBuilder rb{ctx, 4};
+    rb.Push(response_size == 0 ? ResultUnknown : ResultSuccess);
+    rb.Push(response_size);
+}
+
+void SET_SYS::GetSettingsItemValue(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_SET, "called");
+
+    // The category of the setting. This corresponds to the top-level keys of
+    // system_settings.ini.
+    const auto setting_category_buf{ctx.ReadBuffer(0)};
+    const std::string setting_category{setting_category_buf.begin(), setting_category_buf.end()};
+
+    // The name of the setting. This corresponds to the second-level keys of
+    // system_settings.ini.
+    const auto setting_name_buf{ctx.ReadBuffer(1)};
+    const std::string setting_name{setting_name_buf.begin(), setting_name_buf.end()};
+
+    auto settings{GetSettings()};
+    Result response{ResultUnknown};
+
+    if (settings.contains(setting_category) && settings[setting_category].contains(setting_name)) {
+        auto setting_value = settings[setting_category][setting_name];
+        ctx.WriteBuffer(setting_value.data(), setting_value.size());
+        response = ResultSuccess;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(response);
+}
+
 SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
     // clang-format off
     static const FunctionInfo functions[] = {
@@ -138,8 +213,8 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
         {32, nullptr, "SetAccountNotificationSettings"},
         {35, nullptr, "GetVibrationMasterVolume"},
         {36, nullptr, "SetVibrationMasterVolume"},
-        {37, nullptr, "GetSettingsItemValueSize"},
-        {38, nullptr, "GetSettingsItemValue"},
+        {37, &SET_SYS::GetSettingsItemValueSize, "GetSettingsItemValueSize"},
+        {38, &SET_SYS::GetSettingsItemValue, "GetSettingsItemValue"},
         {39, nullptr, "GetTvSettings"},
         {40, nullptr, "SetTvSettings"},
         {41, nullptr, "GetEdid"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index ac97772b78..258ef8c578 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -23,6 +23,8 @@ private:
         BasicBlack = 1,
     };
 
+    void GetSettingsItemValueSize(Kernel::HLERequestContext& ctx);
+    void GetSettingsItemValue(Kernel::HLERequestContext& ctx);
     void GetFirmwareVersion(Kernel::HLERequestContext& ctx);
     void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
     void GetColorSetId(Kernel::HLERequestContext& ctx);

From 9b34afa588c4e4bf312e2812ffe6879e09dafc75 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Mon, 3 Oct 2022 22:52:52 -0400
Subject: [PATCH 2/3] Add implementation of svcCreateSession

---
 src/core/hle/kernel/svc.cpp    | 90 +++++++++++++++++++++++++++++++++-
 src/core/hle/kernel/svc_wrap.h | 14 ++++++
 2 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 27e5a805db..3a89511aa7 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -29,6 +29,7 @@
 #include "core/hle/kernel/k_resource_limit.h"
 #include "core/hle/kernel/k_scheduler.h"
 #include "core/hle/kernel/k_scoped_resource_reservation.h"
+#include "core/hle/kernel/k_session.h"
 #include "core/hle/kernel/k_shared_memory.h"
 #include "core/hle/kernel/k_synchronization_object.h"
 #include "core/hle/kernel/k_thread.h"
@@ -256,6 +257,93 @@ static Result UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u3
     return UnmapMemory(system, dst_addr, src_addr, size);
 }
 
+template <typename T>
+Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client, u64 name) {
+    auto& process = *system.CurrentProcess();
+    auto& handle_table = process.GetHandleTable();
+
+    // Declare the session we're going to allocate.
+    T* session;
+
+    // Reserve a new session from the process resource limit.
+    // FIXME: LimitableResource_SessionCountMax
+    KScopedResourceReservation session_reservation(&process, LimitableResource::Sessions);
+    if (session_reservation.Succeeded()) {
+        session = T::Create(system.Kernel());
+    } else {
+        return ResultLimitReached;
+
+        // // We couldn't reserve a session. Check that we support dynamically expanding the
+        // // resource limit.
+        // R_UNLESS(process.GetResourceLimit() ==
+        //          &system.Kernel().GetSystemResourceLimit(), ResultLimitReached);
+        // R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), ResultLimitReached());
+
+        // // Try to allocate a session from unused slab memory.
+        // session = T::CreateFromUnusedSlabMemory();
+        // R_UNLESS(session != nullptr, ResultLimitReached);
+        // ON_RESULT_FAILURE { session->Close(); };
+
+        // // If we're creating a KSession, we want to add two KSessionRequests to the heap, to
+        // // prevent request exhaustion.
+        // // NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's
+        // // no reason to not do this statically.
+        // if constexpr (std::same_as<T, KSession>) {
+        //     for (size_t i = 0; i < 2; i++) {
+        //         KSessionRequest* request = KSessionRequest::CreateFromUnusedSlabMemory();
+        //         R_UNLESS(request != nullptr, ResultLimitReached);
+        //         request->Close();
+        //     }
+        // }
+
+        // We successfully allocated a session, so add the object we allocated to the resource
+        // limit.
+        // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::Sessions, 1);
+    }
+
+    // Check that we successfully created a session.
+    R_UNLESS(session != nullptr, ResultOutOfResource);
+
+    // Initialize the session.
+    session->Initialize(nullptr, fmt::format("{}", name));
+
+    // Commit the session reservation.
+    session_reservation.Commit();
+
+    // Ensure that we clean up the session (and its only references are handle table) on function
+    // end.
+    SCOPE_EXIT({
+        session->GetClientSession().Close();
+        session->GetServerSession().Close();
+    });
+
+    // Register the session.
+    T::Register(system.Kernel(), session);
+
+    // Add the server session to the handle table.
+    R_TRY(handle_table.Add(out_server, &session->GetServerSession()));
+
+    // Add the client session to the handle table. */
+    const auto result = handle_table.Add(out_client, &session->GetClientSession());
+
+    if (!R_SUCCEEDED(result)) {
+        // Ensure that we maintaing a clean handle state on exit.
+        handle_table.Remove(*out_server);
+    }
+
+    return result;
+}
+
+static Result CreateSession(Core::System& system, Handle* out_server, Handle* out_client,
+                            u32 is_light, u64 name) {
+    if (is_light) {
+        // return CreateSession<KLightSession>(system, out_server, out_client, name);
+        return ResultUnknown;
+    } else {
+        return CreateSession<KSession>(system, out_server, out_client, name);
+    }
+}
+
 /// Connect to an OS service given the port name, returns the handle to the port to out
 static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_name_address) {
     auto& memory = system.Memory();
@@ -2860,7 +2948,7 @@ static const FunctionDef SVC_Table_64[] = {
     {0x3D, SvcWrap64<ChangeKernelTraceState>, "ChangeKernelTraceState"},
     {0x3E, nullptr, "Unknown3e"},
     {0x3F, nullptr, "Unknown3f"},
-    {0x40, nullptr, "CreateSession"},
+    {0x40, SvcWrap64<CreateSession>, "CreateSession"},
     {0x41, nullptr, "AcceptSession"},
     {0x42, nullptr, "ReplyAndReceiveLight"},
     {0x43, nullptr, "ReplyAndReceive"},
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 4bc49087e2..16bf65802a 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -346,6 +346,20 @@ void SvcWrap64(Core::System& system) {
     FuncReturn(system, retval);
 }
 
+// Used by CreateSession
+template <Result func(Core::System&, Handle*, Handle*, u32, u64)>
+void SvcWrap64(Core::System& system) {
+    Handle param_1 = 0;
+    Handle param_2 = 0;
+    const u32 retval = func(system, &param_1, &param_2, static_cast<u32>(Param(system, 2)),
+                            static_cast<u32>(Param(system, 3)))
+                           .raw;
+
+    system.CurrentArmInterface().SetReg(1, param_1);
+    system.CurrentArmInterface().SetReg(2, param_2);
+    FuncReturn(system, retval);
+}
+
 // Used by WaitForAddress
 template <Result func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
 void SvcWrap64(Core::System& system) {

From 61a8696510b3bca120f1f0289a3829e3db14834e Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Tue, 11 Oct 2022 18:16:56 -0400
Subject: [PATCH 3/3] k_server_session: preliminary support for userspace
 server sessions

---
 src/core/arm/dynarmic/arm_dynarmic_64.cpp |   1 +
 src/core/hle/ipc_helpers.h                |   3 +-
 src/core/hle/kernel/k_client_session.cpp  |   5 +-
 src/core/hle/kernel/k_client_session.h    |   3 +-
 src/core/hle/kernel/k_server_session.cpp  | 243 ++++++++++++++++++++--
 src/core/hle/kernel/k_server_session.h    |  37 ++--
 src/core/hle/kernel/svc.cpp               |  82 ++++++--
 src/core/hle/kernel/svc_wrap.h            |  18 ++
 src/core/hle/service/sm/sm.cpp            |   3 +-
 9 files changed, 346 insertions(+), 49 deletions(-)

diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 1d46f6d401..22b5d56568 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -111,6 +111,7 @@ public:
         LOG_ERROR(Core_ARM,
                   "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc,
                   num_instructions, memory.Read32(pc));
+        ReturnException(pc, ARM_Interface::no_execute);
     }
 
     void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index d631c03576..0cc26a2118 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -152,7 +152,8 @@ public:
                 Kernel::LimitableResource::Sessions, 1);
 
             auto* session = Kernel::KSession::Create(kernel);
-            session->Initialize(nullptr, iface->GetServiceName());
+            session->Initialize(nullptr, iface->GetServiceName(),
+                                std::make_shared<Kernel::SessionRequestManager>(kernel));
 
             context->AddMoveObject(&session->GetClientSession());
             iface->ClientConnected(&session->GetServerSession());
diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp
index b2a887b14f..8892c5b7cb 100644
--- a/src/core/hle/kernel/k_client_session.cpp
+++ b/src/core/hle/kernel/k_client_session.cpp
@@ -21,10 +21,9 @@ void KClientSession::Destroy() {
 
 void KClientSession::OnServerClosed() {}
 
-Result KClientSession::SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
-                                       Core::Timing::CoreTiming& core_timing) {
+Result KClientSession::SendSyncRequest() {
     // Signal the server session that new data is available
-    return parent->GetServerSession().HandleSyncRequest(thread, memory, core_timing);
+    return parent->GetServerSession().OnRequest();
 }
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/k_client_session.h b/src/core/hle/kernel/k_client_session.h
index 0c750d7567..b4a19c5460 100644
--- a/src/core/hle/kernel/k_client_session.h
+++ b/src/core/hle/kernel/k_client_session.h
@@ -46,8 +46,7 @@ public:
         return parent;
     }
 
-    Result SendSyncRequest(KThread* thread, Core::Memory::Memory& memory,
-                           Core::Timing::CoreTiming& core_timing);
+    Result SendSyncRequest();
 
     void OnServerClosed();
 
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index 802c646a6d..4252c9adbd 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -7,6 +7,8 @@
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
+#include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/hle_ipc.h"
@@ -18,13 +20,19 @@
 #include "core/hle/kernel/k_server_session.h"
 #include "core/hle/kernel/k_session.h"
 #include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_thread_queue.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/service_thread.h"
 #include "core/memory.h"
 
 namespace Kernel {
 
-KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
+using ThreadQueueImplForKServerSessionRequest = KThreadQueue;
+
+static constexpr u32 MessageBufferSize = 0x100;
+
+KServerSession::KServerSession(KernelCore& kernel_)
+    : KSynchronizationObject{kernel_}, m_lock{kernel_} {}
 
 KServerSession::~KServerSession() = default;
 
@@ -33,17 +41,14 @@ void KServerSession::Initialize(KSession* parent_session_, std::string&& name_,
     // Set member variables.
     parent = parent_session_;
     name = std::move(name_);
-
-    if (manager_) {
-        manager = manager_;
-    } else {
-        manager = std::make_shared<SessionRequestManager>(kernel);
-    }
+    manager = manager_;
 }
 
 void KServerSession::Destroy() {
     parent->OnServerClosed();
 
+    this->CleanupRequests();
+
     parent->Close();
 
     // Release host emulation members.
@@ -54,13 +59,13 @@ void KServerSession::Destroy() {
 }
 
 void KServerSession::OnClientClosed() {
-    if (manager->HasSessionHandler()) {
+    if (manager && manager->HasSessionHandler()) {
         manager->SessionHandler().ClientDisconnected(this);
     }
 }
 
 bool KServerSession::IsSignaled() const {
-    ASSERT(kernel.GlobalSchedulerContext().IsLocked());
+    ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
 
     // If the client is closed, we're always signaled.
     if (parent->IsClientClosed()) {
@@ -68,7 +73,7 @@ bool KServerSession::IsSignaled() const {
     }
 
     // Otherwise, we're signaled if we have a request and aren't handling one.
-    return false;
+    return !m_thread_request_list.empty() && m_current_thread_request == nullptr;
 }
 
 void KServerSession::AppendDomainHandler(SessionRequestHandlerPtr handler) {
@@ -173,9 +178,221 @@ Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
     return result;
 }
 
-Result KServerSession::HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
-                                         Core::Timing::CoreTiming& core_timing) {
-    return QueueSyncRequest(thread, memory);
+Result KServerSession::OnRequest() {
+    // Create the wait queue.
+    ThreadQueueImplForKServerSessionRequest wait_queue{kernel};
+
+    {
+        // Lock the scheduler.
+        KScopedSchedulerLock sl{kernel};
+
+        // Ensure that we can handle new requests.
+        R_UNLESS(!parent->IsServerClosed(), ResultSessionClosed);
+
+        // Check that we're not terminating.
+        R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested);
+
+        if (manager) {
+            // HLE request.
+            auto& memory{kernel.System().Memory()};
+            this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory);
+        } else {
+            // Non-HLE request.
+            auto* thread{GetCurrentThreadPointer(kernel)};
+
+            // Get whether we're empty.
+            const bool was_empty = m_thread_request_list.empty();
+
+            // Add the thread to the list.
+            thread->Open();
+            m_thread_request_list.push_back(thread);
+
+            // If we were empty, signal.
+            if (was_empty) {
+                this->NotifyAvailable();
+            }
+        }
+
+        // This is a synchronous request, so we should wait for our request to complete.
+        GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
+        GetCurrentThread(kernel).BeginWait(&wait_queue);
+    }
+
+    return GetCurrentThread(kernel).GetWaitResult();
+}
+
+Result KServerSession::SendReply() {
+    // Lock the session.
+    KScopedLightLock lk(m_lock);
+
+    // Get the request.
+    KThread* client_thread;
+    {
+        KScopedSchedulerLock sl{kernel};
+
+        // Get the current request.
+        client_thread = m_current_thread_request;
+        R_UNLESS(client_thread != nullptr, ResultInvalidState);
+
+        // Clear the current request, since we're processing it.
+        m_current_thread_request = nullptr;
+        if (!m_thread_request_list.empty()) {
+            this->NotifyAvailable();
+        }
+    }
+
+    // Close reference to the request once we're done processing it.
+    SCOPE_EXIT({ client_thread->Close(); });
+
+    // Extract relevant information from the request.
+    // const uintptr_t client_message  = request->GetAddress();
+    // const size_t client_buffer_size = request->GetSize();
+    // KThread *client_thread          = request->GetThread();
+    // KEvent *event                   = request->GetEvent();
+
+    // Check whether we're closed.
+    const bool closed = (client_thread == nullptr || parent->IsClientClosed());
+
+    Result result = ResultSuccess;
+    if (!closed) {
+        // If we're not closed, send the reply.
+        Core::Memory::Memory& memory{kernel.System().Memory()};
+        KThread* server_thread{GetCurrentThreadPointer(kernel)};
+        UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+
+        auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+        auto* dst_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
+        std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
+    } else {
+        result = ResultSessionClosed;
+    }
+
+    // Select a result for the client.
+    Result client_result = result;
+    if (closed && R_SUCCEEDED(result)) {
+        result = ResultSessionClosed;
+        client_result = ResultSessionClosed;
+    } else {
+        result = ResultSuccess;
+    }
+
+    // If there's a client thread, update it.
+    if (client_thread != nullptr) {
+        // End the client thread's wait.
+        KScopedSchedulerLock sl{kernel};
+
+        if (!client_thread->IsTerminationRequested()) {
+            client_thread->EndWait(client_result);
+        }
+    }
+
+    return result;
+}
+
+Result KServerSession::ReceiveRequest() {
+    // Lock the session.
+    KScopedLightLock lk(m_lock);
+
+    // Get the request and client thread.
+    // KSessionRequest *request;
+    KThread* client_thread;
+
+    {
+        KScopedSchedulerLock sl{kernel};
+
+        // Ensure that we can service the request.
+        R_UNLESS(!parent->IsClientClosed(), ResultSessionClosed);
+
+        // Ensure we aren't already servicing a request.
+        R_UNLESS(m_current_thread_request == nullptr, ResultNotFound);
+
+        // Ensure we have a request to service.
+        R_UNLESS(!m_thread_request_list.empty(), ResultNotFound);
+
+        // Pop the first request from the list.
+        client_thread = m_thread_request_list.front();
+        m_thread_request_list.pop_front();
+
+        // Get the thread for the request.
+        R_UNLESS(client_thread != nullptr, ResultSessionClosed);
+
+        // Open the client thread.
+        client_thread->Open();
+    }
+
+    // SCOPE_EXIT({ client_thread->Close(); });
+
+    // Set the request as our current.
+    m_current_thread_request = client_thread;
+
+    // Receive the message.
+    Core::Memory::Memory& memory{kernel.System().Memory()};
+    KThread* server_thread{GetCurrentThreadPointer(kernel)};
+    UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+
+    auto* src_msg_buffer = memory.GetPointer(client_thread->GetTLSAddress());
+    auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+    std::memcpy(dst_msg_buffer, src_msg_buffer, MessageBufferSize);
+
+    // We succeeded.
+    return ResultSuccess;
+}
+
+void KServerSession::CleanupRequests() {
+    KScopedLightLock lk(m_lock);
+
+    // Clean up any pending requests.
+    while (true) {
+        // Get the next request.
+        // KSessionRequest *request = nullptr;
+        KThread* client_thread = nullptr;
+        {
+            KScopedSchedulerLock sl{kernel};
+
+            if (m_current_thread_request) {
+                // Choose the current request if we have one.
+                client_thread = m_current_thread_request;
+                m_current_thread_request = nullptr;
+            } else if (!m_thread_request_list.empty()) {
+                // Pop the request from the front of the list.
+                client_thread = m_thread_request_list.front();
+                m_thread_request_list.pop_front();
+            }
+        }
+
+        // If there's no request, we're done.
+        if (client_thread == nullptr) {
+            break;
+        }
+
+        // Close a reference to the request once it's cleaned up.
+        SCOPE_EXIT({ client_thread->Close(); });
+
+        // Extract relevant information from the request.
+        // const uintptr_t client_message  = request->GetAddress();
+        // const size_t client_buffer_size = request->GetSize();
+        // KThread *client_thread          = request->GetThread();
+        // KEvent *event                   = request->GetEvent();
+
+        // KProcess *server_process             = request->GetServerProcess();
+        // KProcess *client_process             = (client_thread != nullptr) ?
+        //                                         client_thread->GetOwnerProcess() : nullptr;
+        // KProcessPageTable *client_page_table = (client_process != nullptr) ?
+        //                                         &client_process->GetPageTable() : nullptr;
+
+        // Cleanup the mappings.
+        // Result result = CleanupMap(request, server_process, client_page_table);
+
+        // If there's a client thread, update it.
+        if (client_thread != nullptr) {
+            // End the client thread's wait.
+            KScopedSchedulerLock sl{kernel};
+
+            if (!client_thread->IsTerminationRequested()) {
+                client_thread->EndWait(ResultSessionClosed);
+            }
+        }
+    }
 }
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 6d08219453..748d52826c 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -3,6 +3,7 @@
 
 #pragma once
 
+#include <list>
 #include <memory>
 #include <string>
 #include <utility>
@@ -10,6 +11,7 @@
 #include <boost/intrusive/list.hpp>
 
 #include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_light_lock.h"
 #include "core/hle/kernel/k_synchronization_object.h"
 #include "core/hle/result.h"
 
@@ -59,25 +61,15 @@ public:
     void OnClientClosed();
 
     void ClientConnected(SessionRequestHandlerPtr handler) {
-        manager->SetSessionHandler(std::move(handler));
+        if (manager) {
+            manager->SetSessionHandler(std::move(handler));
+        }
     }
 
     void ClientDisconnected() {
         manager = nullptr;
     }
 
-    /**
-     * Handle a sync request from the emulated application.
-     *
-     * @param thread      Thread that initiated the request.
-     * @param memory      Memory context to handle the sync request under.
-     * @param core_timing Core timing context to schedule the request event under.
-     *
-     * @returns Result from the operation.
-     */
-    Result HandleSyncRequest(KThread* thread, Core::Memory::Memory& memory,
-                             Core::Timing::CoreTiming& core_timing);
-
     /// Adds a new domain request handler to the collection of request handlers within
     /// this ServerSession instance.
     void AppendDomainHandler(SessionRequestHandlerPtr handler);
@@ -88,7 +80,7 @@ public:
 
     /// Returns true if the session has been converted to a domain, otherwise False
     bool IsDomain() const {
-        return manager->IsDomain();
+        return manager && manager->IsDomain();
     }
 
     /// Converts the session to a domain at the end of the current command
@@ -101,7 +93,15 @@ public:
         return manager;
     }
 
+    /// TODO: flesh these out to match the real kernel
+    Result OnRequest();
+    Result SendReply();
+    Result ReceiveRequest();
+
 private:
+    /// Frees up waiting client sessions when this server session is about to die
+    void CleanupRequests();
+
     /// Queues a sync request from the emulated application.
     Result QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
 
@@ -112,7 +112,7 @@ private:
     /// object handle.
     Result HandleDomainSyncRequest(Kernel::HLERequestContext& context);
 
-    /// This session's HLE request handlers
+    /// This session's HLE request handlers; if nullptr, this is not an HLE server
     std::shared_ptr<SessionRequestManager> manager;
 
     /// When set to True, converts the session to a domain at the end of the command
@@ -120,6 +120,13 @@ private:
 
     /// KSession that owns this KServerSession
     KSession* parent{};
+
+    /// List of threads which are pending a reply.
+    /// FIXME: KSessionRequest
+    std::list<KThread*> m_thread_request_list;
+    KThread* m_current_thread_request{};
+
+    KLightLock m_lock;
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 3a89511aa7..510a9b3e3a 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -323,7 +323,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien
     // Add the server session to the handle table.
     R_TRY(handle_table.Add(out_server, &session->GetServerSession()));
 
-    // Add the client session to the handle table. */
+    // Add the client session to the handle table.
     const auto result = handle_table.Add(out_client, &session->GetClientSession());
 
     if (!R_SUCCEEDED(result)) {
@@ -383,7 +383,8 @@ static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_n
 
     // Create a session.
     KClientSession* session{};
-    R_TRY(port->CreateSession(std::addressof(session)));
+    R_TRY(port->CreateSession(std::addressof(session),
+                              std::make_shared<SessionRequestManager>(kernel)));
     port->Close();
 
     // Register the session in the table, close the extra reference.
@@ -401,7 +402,7 @@ static Result ConnectToNamedPort32(Core::System& system, Handle* out_handle,
     return ConnectToNamedPort(system, out_handle, port_name_address);
 }
 
-/// Makes a blocking IPC call to an OS service.
+/// Makes a blocking IPC call to a service.
 static Result SendSyncRequest(Core::System& system, Handle handle) {
     auto& kernel = system.Kernel();
 
@@ -415,22 +416,75 @@ static Result SendSyncRequest(Core::System& system, Handle handle) {
 
     LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
 
-    {
-        KScopedSchedulerLock lock(kernel);
-
-        // This is a synchronous request, so we should wait for our request to complete.
-        GetCurrentThread(kernel).BeginWait(std::addressof(wait_queue));
-        GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC);
-        session->SendSyncRequest(&GetCurrentThread(kernel), system.Memory(), system.CoreTiming());
-    }
-
-    return GetCurrentThread(kernel).GetWaitResult();
+    return session->SendSyncRequest();
 }
 
 static Result SendSyncRequest32(Core::System& system, Handle handle) {
     return SendSyncRequest(system, handle);
 }
 
+static Result ReplyAndReceive(Core::System& system, s32* out_index, Handle* handles,
+                              s32 num_handles, Handle reply_target, s64 timeout_ns) {
+    auto& kernel = system.Kernel();
+    auto& handle_table = GetCurrentThread(kernel).GetOwnerProcess()->GetHandleTable();
+
+    // Convert handle list to object table.
+    std::vector<KSynchronizationObject*> objs(num_handles);
+    R_UNLESS(
+        handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, num_handles),
+        ResultInvalidHandle);
+
+    // Ensure handles are closed when we're done.
+    SCOPE_EXIT({
+        for (auto i = 0; i < num_handles; ++i) {
+            objs[i]->Close();
+        }
+    });
+
+    // Reply to the target, if one is specified.
+    if (reply_target != InvalidHandle) {
+        KScopedAutoObject session = handle_table.GetObject<KServerSession>(reply_target);
+        R_UNLESS(session.IsNotNull(), ResultInvalidHandle);
+
+        // If we fail to reply, we want to set the output index to -1.
+        // ON_RESULT_FAILURE { *out_index = -1; };
+
+        // Send the reply.
+        // R_TRY(session->SendReply());
+
+        Result rc = session->SendReply();
+        if (!R_SUCCEEDED(rc)) {
+            *out_index = -1;
+            return rc;
+        }
+    }
+
+    // Wait for a message.
+    while (true) {
+        // Wait for an object.
+        s32 index;
+        Result result = KSynchronizationObject::Wait(kernel, &index, objs.data(),
+                                                     static_cast<s32>(objs.size()), timeout_ns);
+        if (result == ResultTimedOut) {
+            return result;
+        }
+
+        // Receive the request.
+        if (R_SUCCEEDED(result)) {
+            KServerSession* session = objs[index]->DynamicCast<KServerSession*>();
+            if (session != nullptr) {
+                result = session->ReceiveRequest();
+                if (result == ResultNotFound) {
+                    continue;
+                }
+            }
+        }
+
+        *out_index = index;
+        return result;
+    }
+}
+
 /// Get the ID for the specified thread.
 static Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) {
     // Get the thread from its handle.
@@ -2951,7 +3005,7 @@ static const FunctionDef SVC_Table_64[] = {
     {0x40, SvcWrap64<CreateSession>, "CreateSession"},
     {0x41, nullptr, "AcceptSession"},
     {0x42, nullptr, "ReplyAndReceiveLight"},
-    {0x43, nullptr, "ReplyAndReceive"},
+    {0x43, SvcWrap64<ReplyAndReceive>, "ReplyAndReceive"},
     {0x44, nullptr, "ReplyAndReceiveWithUserBuffer"},
     {0x45, SvcWrap64<CreateEvent>, "CreateEvent"},
     {0x46, nullptr, "MapIoRegion"},
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 16bf65802a..272c54cf75 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -8,6 +8,7 @@
 #include "core/core.h"
 #include "core/hle/kernel/svc_types.h"
 #include "core/hle/result.h"
+#include "core/memory.h"
 
 namespace Kernel {
 
@@ -360,6 +361,23 @@ void SvcWrap64(Core::System& system) {
     FuncReturn(system, retval);
 }
 
+// Used by ReplyAndReceive
+template <Result func(Core::System&, s32*, Handle*, s32, Handle, s64)>
+void SvcWrap64(Core::System& system) {
+    s32 param_1 = 0;
+    s32 num_handles = static_cast<s32>(Param(system, 2));
+
+    std::vector<Handle> handles(num_handles);
+    system.Memory().ReadBlock(Param(system, 1), handles.data(), num_handles * sizeof(Handle));
+
+    const u32 retval = func(system, &param_1, handles.data(), num_handles,
+                            static_cast<s32>(Param(system, 3)), static_cast<s64>(Param(system, 4)))
+                           .raw;
+
+    system.CurrentArmInterface().SetReg(1, param_1);
+    FuncReturn(system, retval);
+}
+
 // Used by WaitForAddress
 template <Result func(Core::System&, u64, Svc::ArbitrationType, s32, s64)>
 void SvcWrap64(Core::System& system) {
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 246c946236..48e70f93c7 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -156,7 +156,8 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
 
     // Create a new session.
     Kernel::KClientSession* session{};
-    if (const auto result = port->GetClientPort().CreateSession(std::addressof(session));
+    if (const auto result = port->GetClientPort().CreateSession(
+            std::addressof(session), std::make_shared<Kernel::SessionRequestManager>(kernel));
         result.IsError()) {
         LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw);
         return result;