From b8fb9b3f112cb43831aeac8ab1242ae653989067 Mon Sep 17 00:00:00 2001
From: bunnei <bunneidev@gmail.com>
Date: Tue, 8 Jun 2021 13:39:20 -0700
Subject: [PATCH] hle: kernel: KServerSession: Work-around scenario where
 session is closed too early.

---
 src/core/hle/kernel/k_server_session.cpp | 31 ++++++++++++++++++------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index dd62706a8..5c3c13ce6 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -8,6 +8,7 @@
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/hle_ipc.h"
@@ -119,11 +120,20 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor
 
     context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
 
+    // In the event that something fails here, stub a result to prevent the game from crashing.
+    // This is a work-around in the event that somehow we process a service request after the
+    // session has been closed by the game. This has been observed to happen rarely in Pokemon
+    // Sword/Shield and is likely a result of us using host threads/scheduling for services.
+    // TODO(bunnei): Find a better solution here.
+    auto error_guard = SCOPE_GUARD({ CompleteSyncRequest(*context); });
+
     // Ensure we have a session request handler
     if (manager->HasSessionRequestHandler(*context)) {
         if (auto strong_ptr = manager->GetServiceThread().lock()) {
             strong_ptr->QueueSyncRequest(*parent, std::move(context));
-            return ResultSuccess;
+
+            // We succeeded.
+            error_guard.Cancel();
         } else {
             ASSERT_MSG(false, "strong_ptr is nullptr!");
         }
@@ -136,13 +146,20 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor
 
 ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
     ResultCode result = ResultSuccess;
+
     // If the session has been converted to a domain, handle the domain request
-    if (IsDomain() && context.HasDomainMessageHeader()) {
-        result = HandleDomainSyncRequest(context);
-        // If there is no domain header, the regular session handler is used
-    } else if (manager->HasSessionHandler()) {
-        // If this ServerSession has an associated HLE handler, forward the request to it.
-        result = manager->SessionHandler().HandleSyncRequest(*this, context);
+    if (manager->HasSessionRequestHandler(context)) {
+        if (IsDomain() && context.HasDomainMessageHeader()) {
+            result = HandleDomainSyncRequest(context);
+            // If there is no domain header, the regular session handler is used
+        } else if (manager->HasSessionHandler()) {
+            // If this ServerSession has an associated HLE handler, forward the request to it.
+            result = manager->SessionHandler().HandleSyncRequest(*this, context);
+        }
+    } else {
+        ASSERT_MSG(false, "Session handler is invalid, stubbing response!");
+        IPC::ResponseBuilder rb(context, 2);
+        rb.Push(ResultSuccess);
     }
 
     if (convert_to_domain) {