From 13a8fde3ad2a4a37cf1bb8dcb367b4c8fc8b4d9b Mon Sep 17 00:00:00 2001
From: Michael Scire <SciresM@gmail.com>
Date: Sun, 7 Jul 2019 09:42:54 -0700
Subject: [PATCH] Implement MapPhysicalMemory/UnmapPhysicalMemory

This implements svcMapPhysicalMemory/svcUnmapPhysicalMemory for Yuzu,
which can be used to map memory at a desired address by games since
3.0.0.

It also properly parses SystemResourceSize from NPDM, and makes
information available via svcGetInfo.

This is needed for games like Super Smash Bros. and Diablo 3 -- this
PR's implementation does not run into the "ASCII reads" issue mentioned
in the comments of #2626, which was caused by the following bugs in
Yuzu's memory management that this PR also addresses:
* Yuzu's memory coalescing does not properly merge blocks. This results
  in a polluted address space/svcQueryMemory results that would be
  impossible to replicate on hardware, which can lead to game code making
  the wrong assumptions about memory layout.
  * This implements better merging for AllocatedMemoryBlocks.
* Yuzu's implementation of svcMirrorMemory unprotected the entire
  virtual memory range containing the range being mirrored. This could
  lead to games attempting to map data at that unprotected
  range/attempting to access that range after yuzu improperly unmapped
  it.
  * This PR fixes it by simply calling ReprotectRange instead of
    Reprotect.
---
 src/core/file_sys/program_metadata.cpp |   4 +
 src/core/file_sys/program_metadata.h   |   4 +-
 src/core/hle/kernel/process.cpp        |   1 +
 src/core/hle/kernel/process.h          |  11 +-
 src/core/hle/kernel/svc.cpp            | 110 ++++++++-
 src/core/hle/kernel/svc_wrap.h         |   5 +
 src/core/hle/kernel/vm_manager.cpp     | 320 ++++++++++++++++++++++++-
 src/core/hle/kernel/vm_manager.h       |  41 +++-
 8 files changed, 475 insertions(+), 21 deletions(-)

diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index eb76174c5b..7310b36026 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -94,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
     return aci_file_access.permissions;
 }
 
+u32 ProgramMetadata::GetSystemResourceSize() const {
+    return npdm_header.system_resource_size;
+}
+
 const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
     return aci_kernel_capabilities;
 }
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 43bf2820a9..88ec97d85f 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -58,6 +58,7 @@ public:
     u32 GetMainThreadStackSize() const;
     u64 GetTitleID() const;
     u64 GetFilesystemPermissions() const;
+    u32 GetSystemResourceSize() const;
     const KernelCapabilityDescriptors& GetKernelCapabilities() const;
 
     void Print() const;
@@ -76,7 +77,8 @@ private:
         u8 reserved_3;
         u8 main_thread_priority;
         u8 main_thread_cpu;
-        std::array<u8, 8> reserved_4;
+        std::array<u8, 4> reserved_4;
+        u32_le system_resource_size;
         u32_le process_category;
         u32_le main_stack_size;
         std::array<u8, 0x10> application_name;
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index f45ef05f69..51245cbb46 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -172,6 +172,7 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
     program_id = metadata.GetTitleID();
     ideal_core = metadata.GetMainThreadCore();
     is_64bit_process = metadata.Is64BitProgram();
+    system_resource_size = metadata.GetSystemResourceSize();
 
     vm_manager.Reset(metadata.GetAddressSpaceType());
 
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 83ea02beec..b0e795577b 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -168,8 +168,9 @@ public:
         return capabilities.GetPriorityMask();
     }
 
-    u32 IsVirtualMemoryEnabled() const {
-        return is_virtual_address_memory_enabled;
+    /// Gets the amount of secure memory to allocate for memory management.
+    u32 GetSystemResourceSize() const {
+        return system_resource_size;
     }
 
     /// Whether this process is an AArch64 or AArch32 process.
@@ -298,12 +299,16 @@ private:
     /// Title ID corresponding to the process
     u64 program_id = 0;
 
+    /// Specifies additional memory to be reserved for the process's memory management by the
+    /// system. When this is non-zero, secure memory is allocated and used for page table allocation
+    /// instead of using the normal global page tables/memory block management.
+    u32 system_resource_size = 0;
+
     /// Resource limit descriptor for this process
     SharedPtr<ResourceLimit> resource_limit;
 
     /// The ideal CPU core for this process, threads are scheduled on this core by default.
     u8 ideal_core = 0;
-    u32 is_virtual_address_memory_enabled = 0;
 
     /// The Thread Local Storage area is allocated as processes create threads,
     /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 332573a955..abb3748921 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -729,8 +729,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
         StackRegionBaseAddr = 14,
         StackRegionSize = 15,
         // 3.0.0+
-        IsVirtualAddressMemoryEnabled = 16,
-        PersonalMmHeapUsage = 17,
+        SystemResourceSize = 16,
+        SystemResourceUsage = 17,
         TitleId = 18,
         // 4.0.0+
         PrivilegedProcessId = 19,
@@ -756,8 +756,8 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
     case GetInfoType::StackRegionSize:
     case GetInfoType::TotalPhysicalMemoryAvailable:
     case GetInfoType::TotalPhysicalMemoryUsed:
-    case GetInfoType::IsVirtualAddressMemoryEnabled:
-    case GetInfoType::PersonalMmHeapUsage:
+    case GetInfoType::SystemResourceSize:
+    case GetInfoType::SystemResourceUsage:
     case GetInfoType::TitleId:
     case GetInfoType::UserExceptionContextAddr:
     case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
@@ -822,8 +822,22 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
             *result = process->GetTotalPhysicalMemoryUsed();
             return RESULT_SUCCESS;
 
-        case GetInfoType::IsVirtualAddressMemoryEnabled:
-            *result = process->IsVirtualMemoryEnabled();
+        case GetInfoType::SystemResourceSize:
+            *result = process->GetSystemResourceSize();
+            return RESULT_SUCCESS;
+
+        case GetInfoType::SystemResourceUsage:
+            // On hardware, this returns the amount of system resource memory that has
+            // been used by the kernel. This is problematic for Yuzu to emulate, because
+            // system resource memory is used for page tables -- and yuzu doesn't really
+            // have a way to calculate how much memory is required for page tables for
+            // the current process at any given time.
+            // TODO: Is this even worth implementing? No game should ever use it, since
+            // the amount of remaining page table space should never be relevant except
+            // for diagnostics. Is returning a value other than zero wise?
+            LOG_WARNING(Kernel_SVC,
+                        "(STUBBED) Attempted to query system resource usage, returned 0");
+            *result = 0;
             return RESULT_SUCCESS;
 
         case GetInfoType::TitleId:
@@ -946,6 +960,86 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
     }
 }
 
+/// Maps memory at a desired address
+static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+    LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
+
+    if (!Common::Is4KBAligned(addr)) {
+        LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (!Common::Is4KBAligned(size)) {
+        LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
+        return ERR_INVALID_SIZE;
+    }
+
+    if (size == 0) {
+        LOG_ERROR(Kernel_SVC, "Size is zero");
+        return ERR_INVALID_SIZE;
+    }
+
+    if (!(addr < addr + size)) {
+        LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
+        return ERR_INVALID_MEMORY_RANGE;
+    }
+
+    auto* const current_process = Core::CurrentProcess();
+    auto& vm_manager = current_process->VMManager();
+
+    if (current_process->GetSystemResourceSize() == 0) {
+        LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
+        return ERR_INVALID_STATE;
+    }
+
+    if (!vm_manager.IsWithinMapRegion(addr, size)) {
+        LOG_ERROR(Kernel_SVC, "Range not within map region");
+        return ERR_INVALID_MEMORY_RANGE;
+    }
+
+    return vm_manager.MapPhysicalMemory(addr, size);
+}
+
+/// Unmaps memory previously mapped via MapPhysicalMemory
+static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
+    LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
+
+    if (!Common::Is4KBAligned(addr)) {
+        LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
+        return ERR_INVALID_ADDRESS;
+    }
+
+    if (!Common::Is4KBAligned(size)) {
+        LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
+        return ERR_INVALID_SIZE;
+    }
+
+    if (size == 0) {
+        LOG_ERROR(Kernel_SVC, "Size is zero");
+        return ERR_INVALID_SIZE;
+    }
+
+    if (!(addr < addr + size)) {
+        LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
+        return ERR_INVALID_MEMORY_RANGE;
+    }
+
+    auto* const current_process = Core::CurrentProcess();
+    auto& vm_manager = current_process->VMManager();
+
+    if (current_process->GetSystemResourceSize() == 0) {
+        LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
+        return ERR_INVALID_STATE;
+    }
+
+    if (!vm_manager.IsWithinMapRegion(addr, size)) {
+        LOG_ERROR(Kernel_SVC, "Range not within map region");
+        return ERR_INVALID_MEMORY_RANGE;
+    }
+
+    return vm_manager.UnmapPhysicalMemory(addr, size);
+}
+
 /// Sets the thread activity
 static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) {
     LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity);
@@ -2303,8 +2397,8 @@ static const FunctionDef SVC_Table[] = {
     {0x29, SvcWrap<GetInfo>, "GetInfo"},
     {0x2A, nullptr, "FlushEntireDataCache"},
     {0x2B, nullptr, "FlushDataCache"},
-    {0x2C, nullptr, "MapPhysicalMemory"},
-    {0x2D, nullptr, "UnmapPhysicalMemory"},
+    {0x2C, SvcWrap<MapPhysicalMemory>, "MapPhysicalMemory"},
+    {0x2D, SvcWrap<UnmapPhysicalMemory>, "UnmapPhysicalMemory"},
     {0x2E, nullptr, "GetFutureThreadInfo"},
     {0x2F, nullptr, "GetLastThreadInfo"},
     {0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"},
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 865473c6fa..c2d8d0dc30 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -32,6 +32,11 @@ void SvcWrap(Core::System& system) {
     FuncReturn(system, func(system, Param(system, 0)).raw);
 }
 
+template <ResultCode func(Core::System&, u64, u64)>
+void SvcWrap(Core::System& system) {
+    FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw);
+}
+
 template <ResultCode func(Core::System&, u32)>
 void SvcWrap(Core::System& system) {
     FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 501544090a..9385a8697e 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -12,6 +12,8 @@
 #include "core/core.h"
 #include "core/file_sys/program_metadata.h"
 #include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
 #include "core/hle/kernel/vm_manager.h"
 #include "core/memory.h"
 #include "core/memory_setup.h"
@@ -49,9 +51,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
         type != next.type) {
         return false;
     }
-    if (type == VMAType::AllocatedMemoryBlock &&
-        (backing_block != next.backing_block || offset + size != next.offset)) {
-        return false;
+    if (type == VMAType::AllocatedMemoryBlock) {
+        return true;
     }
     if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) {
         return false;
@@ -100,7 +101,7 @@ bool VMManager::IsValidHandle(VMAHandle handle) const {
 ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
                                                           std::shared_ptr<std::vector<u8>> block,
                                                           std::size_t offset, u64 size,
-                                                          MemoryState state) {
+                                                          MemoryState state, VMAPermission perm) {
     ASSERT(block != nullptr);
     ASSERT(offset + size <= block->size());
 
@@ -119,7 +120,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
                                             VMAPermission::ReadWriteExecute);
 
     final_vma.type = VMAType::AllocatedMemoryBlock;
-    final_vma.permissions = VMAPermission::ReadWrite;
+    final_vma.permissions = perm;
     final_vma.state = state;
     final_vma.backing_block = std::move(block);
     final_vma.offset = offset;
@@ -308,6 +309,258 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
     return MakeResult<VAddr>(heap_region_base);
 }
 
+ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
+    const auto last_addr = target + size - 1;
+    VAddr cur_addr = target;
+    std::size_t mapped_size = 0;
+
+    ResultCode result = RESULT_SUCCESS;
+
+    // Check whether we've already mapped the desired memory.
+    {
+        auto vma = FindVMA(target);
+        ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+
+        while (true) {
+            const auto vma_start = vma->second.base;
+            const auto vma_size = vma->second.size;
+            const auto state = vma->second.state;
+
+            // Handle last block.
+            if (last_addr <= (vma_start + vma_size - 1)) {
+                if (state != MemoryState::Unmapped) {
+                    mapped_size += last_addr - cur_addr + 1;
+                }
+                break;
+            }
+
+            if (state != MemoryState::Unmapped) {
+                mapped_size += vma_start + vma_size - cur_addr;
+            }
+            cur_addr = vma_start + vma_size;
+            vma++;
+            ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+        }
+
+        // If we already have the desired amount mapped, we're done.
+        if (mapped_size == size) {
+            return RESULT_SUCCESS;
+        }
+    }
+
+    // Check that we can map the memory we want.
+    const auto res_limit = Core::CurrentProcess()->GetResourceLimit();
+    const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) -
+                                  res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory);
+    if (physmem_remaining < (size - mapped_size)) {
+        return ERR_RESOURCE_LIMIT_EXCEEDED;
+    }
+
+    // Keep track of the memory regions we unmap.
+    std::vector<std::pair<u64, u64>> mapped_regions;
+
+    // Iterate, trying to map memory.
+    // Map initially with VMAPermission::None.
+    {
+        cur_addr = target;
+
+        auto vma = FindVMA(target);
+        ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+
+        while (true) {
+            const auto vma_start = vma->second.base;
+            const auto vma_size = vma->second.size;
+            const auto state = vma->second.state;
+
+            // Handle last block.
+            if (last_addr <= (vma_start + vma_size - 1)) {
+                if (state == MemoryState::Unmapped) {
+                    const auto map_res = MapMemoryBlock(
+                        cur_addr, std::make_shared<std::vector<u8>>(last_addr - cur_addr + 1, 0), 0,
+                        last_addr - cur_addr + 1, MemoryState::Heap, VMAPermission::None);
+                    result = map_res.Code();
+                    if (result.IsSuccess()) {
+                        mapped_regions.push_back(
+                            std::make_pair(cur_addr, last_addr - cur_addr + 1));
+                    }
+                }
+                break;
+            }
+
+            if (state == MemoryState::Unmapped) {
+                const auto map_res = MapMemoryBlock(
+                    cur_addr, std::make_shared<std::vector<u8>>(vma_start + vma_size - cur_addr, 0),
+                    0, vma_start + vma_size - cur_addr, MemoryState::Heap, VMAPermission::None);
+                result = map_res.Code();
+                if (result.IsSuccess()) {
+                    mapped_regions.push_back(
+                        std::make_pair(cur_addr, vma_start + vma_size - cur_addr));
+                } else {
+                    break;
+                }
+            }
+            cur_addr = vma_start + vma_size;
+            vma = FindVMA(cur_addr);
+            ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+        }
+    }
+
+    // If we failed, unmap memory.
+    if (result.IsError()) {
+        for (const auto& it : mapped_regions) {
+            const auto unmap_res = UnmapRange(it.first, it.second);
+            ASSERT_MSG(unmap_res.IsSuccess(), "MapPhysicalMemory un-map on error");
+        }
+
+        return result;
+    }
+
+    // We didn't fail, so reprotect all the memory to ReadWrite.
+    {
+        cur_addr = target;
+
+        auto vma = FindVMA(target);
+        ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+
+        while (true) {
+            const auto vma_start = vma->second.base;
+            const auto vma_size = vma->second.size;
+            const auto state = vma->second.state;
+            const auto perm = vma->second.permissions;
+
+            // Handle last block.
+            if (last_addr <= (vma_start + vma_size - 1)) {
+                if (state == MemoryState::Heap && perm == VMAPermission::None) {
+                    ASSERT_MSG(
+                        ReprotectRange(cur_addr, last_addr - cur_addr + 1, VMAPermission::ReadWrite)
+                            .IsSuccess(),
+                        "MapPhysicalMemory reprotect");
+                }
+                break;
+            }
+
+            if (state == MemoryState::Heap && perm == VMAPermission::None) {
+                ASSERT_MSG(ReprotectRange(cur_addr, vma_start + vma_size - cur_addr,
+                                          VMAPermission::ReadWrite)
+                               .IsSuccess(),
+                           "MapPhysicalMemory reprotect");
+            }
+            cur_addr = vma_start + vma_size;
+            vma = FindVMA(cur_addr);
+            ASSERT_MSG(vma != vma_map.end(), "MapPhysicalMemory vma != end");
+        }
+    }
+
+    // Update amount of mapped physical memory.
+    physical_memory_mapped += size - mapped_size;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
+    auto last_addr = target + size - 1;
+    VAddr cur_addr = target;
+    std::size_t mapped_size = 0;
+
+    ResultCode result = RESULT_SUCCESS;
+
+    // Check how much of the memory is currently mapped.
+    {
+        auto vma = FindVMA(target);
+        ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end");
+
+        while (true) {
+            const auto vma_start = vma->second.base;
+            const auto vma_size = vma->second.size;
+            const auto state = vma->second.state;
+            const auto attr = vma->second.attribute;
+
+            // Memory within region must be free or mapped heap.
+            if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) ||
+                  (state == MemoryState::Unmapped))) {
+                return ERR_INVALID_ADDRESS_STATE;
+            }
+
+            // If this is the last block and it's mapped, update mapped size.
+            if (last_addr <= (vma_start + vma_size - 1)) {
+                if (state == MemoryState::Heap) {
+                    mapped_size += last_addr - cur_addr + 1;
+                }
+                break;
+            }
+
+            if (state == MemoryState::Heap) {
+                mapped_size += vma_start + vma_size - cur_addr;
+            }
+            cur_addr = vma_start + vma_size;
+            vma++;
+            ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end");
+        }
+
+        // If memory is already unmapped, we're done.
+        if (mapped_size == 0) {
+            return RESULT_SUCCESS;
+        }
+    }
+
+    // Keep track of the memory regions we unmap.
+    std::vector<std::pair<u64, u64>> unmapped_regions;
+
+    // Try to unmap regions.
+    {
+        cur_addr = target;
+
+        auto vma = FindVMA(target);
+        ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end");
+
+        while (true) {
+            const auto vma_start = vma->second.base;
+            const auto vma_size = vma->second.size;
+            const auto state = vma->second.state;
+            const auto perm = vma->second.permissions;
+
+            // Handle last block.
+            if (last_addr <= (vma_start + vma_size - 1)) {
+                if (state == MemoryState::Heap) {
+                    result = UnmapRange(cur_addr, last_addr - cur_addr + 1);
+                    if (result.IsSuccess()) {
+                        unmapped_regions.push_back(
+                            std::make_pair(cur_addr, last_addr - cur_addr + 1));
+                    }
+                }
+                break;
+            }
+
+            if (state == MemoryState::Heap) {
+                result = UnmapRange(cur_addr, vma_start + vma_size - cur_addr);
+                if (result.IsSuccess()) {
+                    unmapped_regions.push_back(
+                        std::make_pair(cur_addr, vma_start + vma_size - cur_addr));
+                } else {
+                    break;
+                }
+            }
+
+            cur_addr = vma_start + vma_size;
+            vma = FindVMA(cur_addr);
+            ASSERT_MSG(vma != vma_map.end(), "UnmapPhysicalMemory vma != end");
+        }
+    }
+
+    // If we failed, re-map regions.
+    // TODO: Preserve memory contents?
+    if (result.IsError()) {
+        for (const auto& it : unmapped_regions) {
+            const auto remap_res =
+                MapMemoryBlock(it.first, std::make_shared<std::vector<u8>>(it.second, 0), 0,
+                               it.second, MemoryState::Heap, VMAPermission::None);
+            ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error");
+        }
+    }
+
+    return RESULT_SUCCESS;
+}
+
 ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
     constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
     const auto src_check_result = CheckRangeState(
@@ -455,7 +708,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem
     // Protect mirror with permissions from old region
     Reprotect(new_vma, vma->second.permissions);
     // Remove permissions from old region
-    Reprotect(vma, VMAPermission::None);
+    ReprotectRange(src_addr, size, VMAPermission::None);
 
     return RESULT_SUCCESS;
 }
@@ -588,14 +841,14 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
 VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
     const VMAIter next_vma = std::next(iter);
     if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
-        iter->second.size += next_vma->second.size;
+        MergeAdjacentVMA(iter->second, next_vma->second);
         vma_map.erase(next_vma);
     }
 
     if (iter != vma_map.begin()) {
         VMAIter prev_vma = std::prev(iter);
         if (prev_vma->second.CanBeMergedWith(iter->second)) {
-            prev_vma->second.size += iter->second.size;
+            MergeAdjacentVMA(prev_vma->second, iter->second);
             vma_map.erase(iter);
             iter = prev_vma;
         }
@@ -604,6 +857,57 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
     return iter;
 }
 
+void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) {
+    ASSERT(left.CanBeMergedWith(right));
+
+    // Always merge allocated memory blocks, even when they don't share the same backing block.
+    if (left.type == VMAType::AllocatedMemoryBlock &&
+        (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
+        // Check if we can save work.
+        if (left.offset == 0 && left.size == left.backing_block->size()) {
+            // Fast case: left is an entire backing block.
+            left.backing_block->insert(left.backing_block->end(),
+                                       right.backing_block->begin() + right.offset,
+                                       right.backing_block->begin() + right.offset + right.size);
+        } else {
+            // Slow case: make a new memory block for left and right.
+            auto new_memory = std::make_shared<std::vector<u8>>();
+            new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset,
+                               left.backing_block->begin() + left.offset + left.size);
+            new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset,
+                               right.backing_block->begin() + right.offset + right.size);
+            left.backing_block = new_memory;
+            left.offset = 0;
+        }
+
+        // Page table update is needed, because backing memory changed.
+        left.size += right.size;
+        UpdatePageTableForVMA(left);
+
+        // Update mappings for unicorn.
+        system.ArmInterface(0).UnmapMemory(left.base, left.size);
+        system.ArmInterface(1).UnmapMemory(left.base, left.size);
+        system.ArmInterface(2).UnmapMemory(left.base, left.size);
+        system.ArmInterface(3).UnmapMemory(left.base, left.size);
+
+        system.ArmInterface(0).MapBackingMemory(left.base, left.size,
+                                                left.backing_block->data() + left.offset,
+                                                VMAPermission::ReadWriteExecute);
+        system.ArmInterface(1).MapBackingMemory(left.base, left.size,
+                                                left.backing_block->data() + left.offset,
+                                                VMAPermission::ReadWriteExecute);
+        system.ArmInterface(2).MapBackingMemory(left.base, left.size,
+                                                left.backing_block->data() + left.offset,
+                                                VMAPermission::ReadWriteExecute);
+        system.ArmInterface(3).MapBackingMemory(left.base, left.size,
+                                                left.backing_block->data() + left.offset,
+                                                VMAPermission::ReadWriteExecute);
+    } else {
+        // Just update the size.
+        left.size += right.size;
+    }
+}
+
 void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
     switch (vma.type) {
     case VMAType::Free:
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 9fe6ac3f46..16f40ad004 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -349,7 +349,8 @@ public:
      * @param state MemoryState tag to attach to the VMA.
      */
     ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block,
-                                        std::size_t offset, u64 size, MemoryState state);
+                                        std::size_t offset, u64 size, MemoryState state,
+                                        VMAPermission perm = VMAPermission::ReadWrite);
 
     /**
      * Maps an unmanaged host memory pointer at a given address.
@@ -450,6 +451,34 @@ public:
     ///
     ResultVal<VAddr> SetHeapSize(u64 size);
 
+    /// Maps memory at a given address.
+    ///
+    /// @param addr The virtual address to map memory at.
+    /// @param size The amount of memory to map.
+    ///
+    /// @note The destination address must lie within the Map region.
+    ///
+    /// @note This function requires SystemResourceSize is non-zero,
+    ///       however, this is just because if it were not then the
+    ///       resulting page tables could be exploited on hardware by
+    ///       a malicious program. SystemResource usage does not need
+    ///       to be explicitly checked or updated here.
+    ResultCode MapPhysicalMemory(VAddr target, u64 size);
+
+    /// Unmaps memory at a given address.
+    ///
+    /// @param addr The virtual address to unmap memory at.
+    /// @param size The amount of memory to unmap.
+    ///
+    /// @note The destination address must lie within the Map region.
+    ///
+    /// @note This function requires SystemResourceSize is non-zero,
+    ///       however, this is just because if it were not then the
+    ///       resulting page tables could be exploited on hardware by
+    ///       a malicious program. SystemResource usage does not need
+    ///       to be explicitly checked or updated here.
+    ResultCode UnmapPhysicalMemory(VAddr target, u64 size);
+
     /// Maps a region of memory as code memory.
     ///
     /// @param dst_address The base address of the region to create the aliasing memory region.
@@ -657,6 +686,11 @@ private:
      */
     VMAIter MergeAdjacent(VMAIter vma);
 
+    /**
+     * Merges two adjacent VMAs.
+     */
+    void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right);
+
     /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
     void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
 
@@ -742,6 +776,11 @@ private:
     // end of the range. This is essentially 'base_address + current_size'.
     VAddr heap_end = 0;
 
+    // The current amount of memory mapped via MapPhysicalMemory.
+    // This is used here (and in Nintendo's kernel) only for debugging, and does not impact
+    // any behavior.
+    u64 physical_memory_mapped = 0;
+
     Core::System& system;
 };
 } // namespace Kernel