diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp
index 8db9d241f..8a6214409 100644
--- a/src/core/hle/kernel/ipc.cpp
+++ b/src/core/hle/kernel/ipc.cpp
@@ -128,7 +128,11 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread
             u32 num_pages =
                 Common::AlignUp(page_offset + size, Memory::PAGE_SIZE) >> Memory::PAGE_BITS;
 
-            ASSERT(num_pages >= 1);
+            // Skip when the size is zero
+            if (size == 0) {
+                i += 1;
+                break;
+            }
 
             if (reply) {
                 // TODO(Subv): Scan the target's command buffer to make sure that there was a
@@ -139,13 +143,23 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread
                 // process again because they were (presumably) not modified. This behavior is
                 // consistent with the real kernel.
                 if (permissions == IPC::MappedBufferPermissions::R) {
+                    ResultCode result = src_process->vm_manager.UnmapRange(
+                        page_start, num_pages * Memory::PAGE_SIZE);
+                    ASSERT(result == RESULT_SUCCESS);
+                } else {
+                    const auto vma_iter = src_process->vm_manager.vma_map.find(source_address);
+                    const auto& vma = vma_iter->second;
+                    const VAddr dest_address = vma.originating_buffer_address;
+
+                    auto buffer = std::make_shared<std::vector<u8>>(size);
+                    Memory::ReadBlock(*src_process, source_address, buffer->data(), size);
+                    Memory::WriteBlock(*dst_process, dest_address, buffer->data(), size);
+
                     ResultCode result = src_process->vm_manager.UnmapRange(
                         page_start, num_pages * Memory::PAGE_SIZE);
                     ASSERT(result == RESULT_SUCCESS);
                 }
 
-                ASSERT_MSG(permissions == IPC::MappedBufferPermissions::R,
-                           "Unmapping Write MappedBuffers is unimplemented");
                 i += 1;
                 break;
             }
@@ -156,19 +170,13 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread
                 return (address & Memory::PAGE_MASK) == 0;
             };
 
-            // TODO(Subv): Support more than 1 page and aligned page mappings
-            ASSERT_MSG(
-                num_pages == 1 &&
-                    (!IsPageAligned(source_address) || !IsPageAligned(source_address + size)),
-                "MappedBuffers of more than one page or aligned transfers are not implemented");
-
             // TODO(Subv): Perform permission checks.
 
-            // TODO(Subv): Leave a page of Reserved memory before the first page and after the last
+            // TODO(Subv): Leave a page of unmapped memory before the first page and after the last
             // page.
 
-            if (!IsPageAligned(source_address) ||
-                (num_pages == 1 && !IsPageAligned(source_address + size))) {
+            if (num_pages == 1 && !IsPageAligned(source_address) &&
+                !IsPageAligned(source_address + size)) {
                 // If the address of the source buffer is not page-aligned or if the buffer doesn't
                 // fill an entire page, then we have to allocate a page of memory in the target
                 // process and copy over the data from the input buffer. This allocated buffer will
@@ -195,7 +203,24 @@ ResultCode TranslateCommandBuffer(SharedPtr<Thread> src_thread, SharedPtr<Thread
                                               buffer, 0, static_cast<u32>(buffer->size()),
                                               Kernel::MemoryState::Shared)
                         .Unwrap();
+            } else {
+                auto buffer = std::make_shared<std::vector<u8>>(num_pages * Memory::PAGE_SIZE);
+                Memory::ReadBlock(*src_process, source_address, buffer->data() + page_offset, size);
+
+                // Map the pages into the target process' address space.
+                target_address =
+                    dst_process->vm_manager
+                        .MapMemoryBlockToBase(Memory::IPC_MAPPING_VADDR + Memory::PAGE_SIZE,
+                                              Memory::IPC_MAPPING_SIZE - Memory::PAGE_SIZE, buffer,
+                                              0, static_cast<u32>(buffer->size()),
+                                              Kernel::MemoryState::Shared)
+                        .Unwrap();
             }
+            // Save the original address we copied the buffer from so that we can copy the modified
+            // buffer back, if needed
+            auto vma_iter = dst_process->vm_manager.vma_map.find(target_address + page_offset);
+            auto& vma = vma_iter->second;
+            vma.originating_buffer_address = source_address;
 
             cmd_buf[i++] = target_address + page_offset;
             break;
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 7ac5c3b01..fcb107c06 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -86,6 +86,9 @@ struct VirtualMemoryArea {
     PAddr paddr = 0;
     Memory::MMIORegionPointer mmio_handler = nullptr;
 
+    /// Originating address of the IPC mapped buffer
+    VAddr originating_buffer_address = 0;
+
     /// Tests if this area can be merged to the right with `next`.
     bool CanBeMergedWith(const VirtualMemoryArea& next) const;
 };