diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
index 8c244b6b2..2b0d4fdbe 100644
--- a/src/citra_qt/debugger/wait_tree.cpp
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -154,6 +154,9 @@ QString WaitTreeThread::GetText() const {
     case THREADSTATUS_WAIT_SLEEP:
         status = tr("sleeping");
         break;
+    case THREADSTATUS_WAIT_IPC:
+        status = tr("waiting for IPC response");
+        break;
     case THREADSTATUS_WAIT_SYNCH_ALL:
     case THREADSTATUS_WAIT_SYNCH_ANY:
         status = tr("waiting for objects");
@@ -182,6 +185,8 @@ QColor WaitTreeThread::GetColor() const {
         return QColor(Qt::GlobalColor::darkRed);
     case THREADSTATUS_WAIT_SLEEP:
         return QColor(Qt::GlobalColor::darkYellow);
+    case THREADSTATUS_WAIT_IPC:
+        return QColor(Qt::GlobalColor::darkCyan);
     case THREADSTATUS_WAIT_SYNCH_ALL:
     case THREADSTATUS_WAIT_SYNCH_ANY:
         return QColor(Qt::GlobalColor::red);
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7a10b448e..073cb8569 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -62,6 +62,7 @@ set(SRCS
             hle/kernel/timer.cpp
             hle/kernel/vm_manager.cpp
             hle/kernel/wait_object.cpp
+            hle/kernel/ipc.cpp
             hle/lock.cpp
             hle/romfs.cpp
             hle/service/ac/ac.cpp
@@ -248,6 +249,7 @@ set(HEADERS
             hle/kernel/event.h
             hle/kernel/handle_table.h
             hle/kernel/hle_ipc.h
+            hle/kernel/ipc.h
             hle/kernel/kernel.h
             hle/kernel/memory.h
             hle/kernel/mutex.h
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index 004764c63..509ffee58 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -7,7 +7,6 @@
 #include "core/hle/result.h"
 
 namespace Kernel {
-
 namespace ErrCodes {
 enum {
     OutOfHandles = 19,
@@ -18,6 +17,7 @@ enum {
     WrongPermission = 46,
     InvalidBufferDescriptor = 48,
     MaxConnectionsReached = 52,
+    CommandTooLarge = 54,
 };
 }
 
diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp
new file mode 100644
index 000000000..e66ad6dc7
--- /dev/null
+++ b/src/core/hle/kernel/ipc.cpp
@@ -0,0 +1,93 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/ipc.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/ipc.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/memory.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/thread.h"
+#include "core/memory.h"
+
+namespace Kernel {
+
+ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread> dst_thread,
+                                  VAddr src_address, VAddr dst_address) {
+
+    auto& src_process = src_thread->owner_process;
+
+    IPC::Header header;
+    // TODO(Subv): Replace by Memory::Read32 when possible.
+    Memory::ReadBlock(*src_process, src_address, &header.raw, sizeof(header.raw));
+
+    size_t untranslated_size = 1u + header.normal_params_size;
+    size_t command_size = untranslated_size + header.translate_params_size;
+
+    // Note: The real kernel does not check that the command length fits into the IPC buffer area.
+    ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH);
+
+    std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
+    Memory::ReadBlock(*src_process, src_address, cmd_buf.data(), command_size * sizeof(u32));
+
+    size_t i = untranslated_size;
+    while (i < command_size) {
+        u32 descriptor = cmd_buf[i];
+        i += 1;
+
+        switch (IPC::GetDescriptorType(descriptor)) {
+        case IPC::DescriptorType::CopyHandle:
+        case IPC::DescriptorType::MoveHandle: {
+            u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
+            // Note: The real kernel does not check that the number of handles fits into the command
+            // buffer before writing them, only after finishing.
+            if (i + num_handles > command_size) {
+                return ResultCode(ErrCodes::CommandTooLarge, ErrorModule::OS,
+                                  ErrorSummary::InvalidState, ErrorLevel::Status);
+            }
+
+            for (u32 j = 0; j < num_handles; ++j) {
+                Handle handle = cmd_buf[i];
+                SharedPtr<Object> object = nullptr;
+                // Perform pseudo-handle detection here because by the time this function is called,
+                // the current thread and process are no longer the ones which created this IPC
+                // request, but the ones that are handling it.
+                if (handle == CurrentThread) {
+                    object = src_thread;
+                } else if (handle == CurrentProcess) {
+                    object = src_process;
+                } else if (handle != 0) {
+                    object = g_handle_table.GetGeneric(handle);
+                    if (descriptor == IPC::DescriptorType::MoveHandle) {
+                        g_handle_table.Close(handle);
+                    }
+                }
+
+                if (object == nullptr) {
+                    // Note: The real kernel sets invalid translated handles to 0 in the target
+                    // command buffer.
+                    cmd_buf[i++] = 0;
+                    continue;
+                }
+
+                auto result = g_handle_table.Create(std::move(object));
+                cmd_buf[i++] = result.ValueOr(0);
+            }
+            break;
+        }
+        case IPC::DescriptorType::CallingPid: {
+            cmd_buf[i++] = src_process->process_id;
+            break;
+        }
+        default:
+            UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor);
+        }
+    }
+
+    Memory::WriteBlock(*dst_thread->owner_process, dst_address, cmd_buf.data(),
+                       command_size * sizeof(u32));
+
+    return RESULT_SUCCESS;
+}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/ipc.h b/src/core/hle/kernel/ipc.h
new file mode 100644
index 000000000..ac81d1ad4
--- /dev/null
+++ b/src/core/hle/kernel/ipc.h
@@ -0,0 +1,14 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/kernel/thread.h"
+
+namespace Kernel {
+/// Performs IPC command buffer translation from one process to another.
+ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread> dst_thread,
+                                  VAddr src_address, VAddr dst_address);
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 337896abf..bf2e9e376 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -61,13 +61,11 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
 
     // If this ServerSession has an associated HLE handler, forward the request to it.
     if (hle_handler != nullptr) {
-        // Attempt to translate the incoming request's command buffer.
-        ResultCode result = TranslateHLERequest(this);
-        if (result.IsError())
-            return result;
         hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this));
-        // TODO(Subv): Translate the response command buffer.
     } else {
+        // Put the thread to sleep until the server replies, it will be awoken in
+        // svcReplyAndReceive.
+        thread->status = THREADSTATUS_WAIT_IPC;
         // Add the thread to the list of threads that have issued a sync request with this
         // server.
         pending_requesting_threads.push_back(std::move(thread));
@@ -96,8 +94,4 @@ ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& n
     return std::make_tuple(std::move(server_session), std::move(client_session));
 }
 
-ResultCode TranslateHLERequest(ServerSession* server_session) {
-    // TODO(Subv): Implement this function once multiple concurrent processes are supported.
-    return RESULT_SUCCESS;
-}
 } // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index f4360ddf3..477474a25 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -36,6 +36,9 @@ class Thread;
  */
 class ServerSession final : public WaitObject {
 public:
+    std::string GetName() const override {
+        return name;
+    }
     std::string GetTypeName() const override {
         return "ServerSession";
     }
@@ -104,13 +107,4 @@ private:
     static ResultVal<SharedPtr<ServerSession>> Create(std::string name = "Unknown");
 };
 
-/**
- * Performs command buffer translation for an HLE IPC request.
- * The command buffer from the ServerSession thread's TLS is copied into a
- * buffer and all descriptors in the buffer are processed.
- * TODO(Subv): Implement this function, currently we do not support multiple processes running at
- * once, but once that is implemented we'll need to properly translate all descriptors
- * in the command buffer.
- */
-ResultCode TranslateHLERequest(ServerSession* server_session);
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 0f7970ebe..a7c1b4592 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -278,6 +278,7 @@ void Thread::ResumeFromWait() {
     case THREADSTATUS_WAIT_SYNCH_ANY:
     case THREADSTATUS_WAIT_ARB:
     case THREADSTATUS_WAIT_SLEEP:
+    case THREADSTATUS_WAIT_IPC:
         break;
 
     case THREADSTATUS_READY:
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 314fba81f..149a8d1a6 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -35,6 +35,7 @@ enum ThreadStatus {
     THREADSTATUS_READY,          ///< Ready to run
     THREADSTATUS_WAIT_ARB,       ///< Waiting on an address arbiter
     THREADSTATUS_WAIT_SLEEP,     ///< Waiting due to a SleepThread SVC
+    THREADSTATUS_WAIT_IPC,       ///< Waiting for the reply from an IPC request
     THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
     THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
     THREADSTATUS_DORMANT,        ///< Created but not yet made ready
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index fe06af8f8..079a39a7d 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -18,6 +18,7 @@
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/ipc.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/kernel/process.h"
@@ -241,8 +242,6 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) {
 
     Core::System::GetInstance().PrepareReschedule();
 
-    // TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
-    // responds and cause a reschedule.
     return session->SendSyncRequest(Kernel::GetCurrentThread());
 }
 
@@ -455,6 +454,33 @@ static ResultCode WaitSynchronizationN(s32* out, VAddr handles_address, s32 hand
     }
 }
 
+static ResultCode ReceiveIPCRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session,
+                                    Kernel::SharedPtr<Kernel::Thread> thread) {
+    if (server_session->parent->client == nullptr) {
+        return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+    }
+
+    VAddr target_address = thread->GetCommandBufferAddress();
+    VAddr source_address = server_session->currently_handling->GetCommandBufferAddress();
+
+    ResultCode translation_result = Kernel::TranslateCommandBuffer(
+        server_session->currently_handling, thread, source_address, target_address);
+
+    // If a translation error occurred, immediately resume the client thread.
+    if (translation_result.IsError()) {
+        // Set the output of SendSyncRequest in the client thread to the translation output.
+        server_session->currently_handling->SetWaitSynchronizationResult(translation_result);
+
+        server_session->currently_handling->ResumeFromWait();
+        server_session->currently_handling = nullptr;
+
+        // TODO(Subv): This path should try to wait again on the same objects.
+        ASSERT_MSG(false, "ReplyAndReceive translation error behavior unimplemented");
+    }
+
+    return translation_result;
+}
+
 /// In a single operation, sends a IPC reply and waits for a new request.
 static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_count,
                                   Kernel::Handle reply_target) {
@@ -497,7 +523,15 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
             return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
         }
 
-        // TODO(Subv): Perform IPC translation from the current thread to request_thread.
+        VAddr source_address = Kernel::GetCurrentThread()->GetCommandBufferAddress();
+        VAddr target_address = request_thread->GetCommandBufferAddress();
+
+        ResultCode translation_result = Kernel::TranslateCommandBuffer(
+            Kernel::GetCurrentThread(), request_thread, source_address, target_address);
+
+        // Note: The real kernel seems to always panic if the Server->Client buffer translation
+        // fails for whatever reason.
+        ASSERT(translation_result.IsSuccess());
 
         // Note: The scheduler is not invoked here.
         request_thread->ResumeFromWait();
@@ -526,14 +560,11 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
         object->Acquire(thread);
         *index = static_cast<s32>(std::distance(objects.begin(), itr));
 
-        if (object->GetHandleType() == Kernel::HandleType::ServerSession) {
-            auto server_session = static_cast<Kernel::ServerSession*>(object);
-            if (server_session->parent->client == nullptr)
-                return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+        if (object->GetHandleType() != Kernel::HandleType::ServerSession)
+            return RESULT_SUCCESS;
 
-            // TODO(Subv): Perform IPC translation from the ServerSession to the current thread.
-        }
-        return RESULT_SUCCESS;
+        auto server_session = static_cast<Kernel::ServerSession*>(object);
+        return ReceiveIPCRequest(server_session, Kernel::GetCurrentThread());
     }
 
     // No objects were ready to be acquired, prepare to suspend the thread.
@@ -556,10 +587,15 @@ static ResultCode ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_
         ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
         ASSERT(reason == ThreadWakeupReason::Signal);
 
-        thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
-        thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
+        ResultCode result = RESULT_SUCCESS;
 
-        // TODO(Subv): Perform IPC translation upon wakeup.
+        if (object->GetHandleType() == Kernel::HandleType::ServerSession) {
+            auto server_session = Kernel::DynamicObjectCast<Kernel::ServerSession>(object);
+            result = ReceiveIPCRequest(server_session, thread);
+        }
+
+        thread->SetWaitSynchronizationResult(result);
+        thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
     };
 
     Core::System::GetInstance().PrepareReschedule();