diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp
index 94bca03783..b1a72dc0cb 100644
--- a/src/core/hle/config_mem.cpp
+++ b/src/core/hle/config_mem.cpp
@@ -25,10 +25,6 @@ void Init() {
     config_mem.sys_core_ver = 0x2;
     config_mem.unit_info = 0x1; // Bit 0 set for Retail
     config_mem.prev_firm = 0;
-    config_mem.app_mem_type = 0x2; // Default app mem type is 0
-    config_mem.app_mem_alloc = 0x06000000; // Set to 96MB, since some games use more than the default (64MB)
-    config_mem.base_mem_alloc = 0x01400000; // Default base memory is 20MB
-    config_mem.sys_mem_alloc = Memory::FCRAM_SIZE - (config_mem.app_mem_alloc + config_mem.base_mem_alloc);
     config_mem.firm_unk = 0;
     config_mem.firm_version_rev = 0;
     config_mem.firm_version_min = 0x40;
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp
index 98dc8dd582..331b1b22a3 100644
--- a/src/core/hle/hle.cpp
+++ b/src/core/hle/hle.cpp
@@ -34,8 +34,6 @@ void Reschedule(const char *reason) {
 
 void Init() {
     Service::Init();
-    ConfigMem::Init();
-    SharedPage::Init();
 
     g_reschedule = false;
 
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 5711c0405c..7a401a965d 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -7,11 +7,14 @@
 #include "common/assert.h"
 #include "common/logging/log.h"
 
+#include "core/hle/config_mem.h"
 #include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/timer.h"
+#include "core/hle/shared_page.h"
 
 namespace Kernel {
 
@@ -119,6 +122,13 @@ void HandleTable::Clear() {
 
 /// Initialize the kernel
 void Init() {
+    ConfigMem::Init();
+    SharedPage::Init();
+
+    // TODO(yuriks): The memory type parameter needs to be determined by the ExHeader field instead
+    // For now it defaults to the one with a largest allocation to the app
+    Kernel::MemoryInit(2); // Allocates 96MB to the application
+
     Kernel::ResourceLimitsInit();
     Kernel::ThreadingInit();
     Kernel::TimersInit();
@@ -131,11 +141,14 @@ void Init() {
 
 /// Shutdown the kernel
 void Shutdown() {
+    g_handle_table.Clear(); // Free all kernel objects
+
     Kernel::ThreadingShutdown();
+    g_current_process = nullptr;
+
     Kernel::TimersShutdown();
     Kernel::ResourceLimitsShutdown();
-    g_handle_table.Clear(); // Free all kernel objects
-    g_current_process = nullptr;
+    Kernel::MemoryShutdown();
 }
 
 } // namespace
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 57e1912d3d..e69b121eba 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -11,6 +11,7 @@
 #include "common/logging/log.h"
 
 #include "core/hle/config_mem.h"
+#include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/vm_manager.h"
 #include "core/hle/result.h"
 #include "core/hle/shared_page.h"
@@ -19,6 +20,77 @@
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
+namespace Kernel {
+
+static MemoryRegionInfo memory_regions[3];
+
+/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each sytem
+/// memory configuration type.
+static const u32 memory_region_sizes[8][3] = {
+    // Old 3DS layouts
+    {0x04000000, 0x02C00000, 0x01400000}, // 0
+    { /* This appears to be unused. */ }, // 1
+    {0x06000000, 0x00C00000, 0x01400000}, // 2
+    {0x05000000, 0x01C00000, 0x01400000}, // 3
+    {0x04800000, 0x02400000, 0x01400000}, // 4
+    {0x02000000, 0x04C00000, 0x01400000}, // 5
+
+    // New 3DS layouts
+    {0x07C00000, 0x06400000, 0x02000000}, // 6
+    {0x0B200000, 0x02E00000, 0x02000000}, // 7
+};
+
+void MemoryInit(u32 mem_type) {
+    // TODO(yuriks): On the n3DS, all o3DS configurations (<=5) are forced to 6 instead.
+    ASSERT_MSG(mem_type <= 5, "New 3DS memory configuration aren't supported yet!");
+    ASSERT(mem_type != 1);
+
+    // The kernel allocation regions (APPLICATION, SYSTEM and BASE) are laid out in sequence, with
+    // the sizes specified in the memory_region_sizes table.
+    VAddr base = 0;
+    for (int i = 0; i < 3; ++i) {
+        memory_regions[i].base = base;
+        memory_regions[i].size = memory_region_sizes[mem_type][i];
+        memory_regions[i].linear_heap_memory = std::make_shared<std::vector<u8>>();
+
+        base += memory_regions[i].size;
+    }
+
+    // We must've allocated the entire FCRAM by the end
+    ASSERT(base == Memory::FCRAM_SIZE);
+
+    using ConfigMem::config_mem;
+    config_mem.app_mem_type = mem_type;
+    // app_mem_malloc does not always match the configured size for memory_region[0]: in case the
+    // n3DS type override is in effect it reports the size the game expects, not the real one.
+    config_mem.app_mem_alloc = memory_region_sizes[mem_type][0];
+    config_mem.sys_mem_alloc = memory_regions[1].size;
+    config_mem.base_mem_alloc = memory_regions[2].size;
+}
+
+void MemoryShutdown() {
+    for (auto& region : memory_regions) {
+        region.base = 0;
+        region.size = 0;
+        region.linear_heap_memory = nullptr;
+    }
+}
+
+MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) {
+    switch (region) {
+    case MemoryRegion::APPLICATION:
+        return &memory_regions[0];
+    case MemoryRegion::SYSTEM:
+        return &memory_regions[1];
+    case MemoryRegion::BASE:
+        return &memory_regions[2];
+    default:
+        UNREACHABLE();
+    }
+}
+
+}
+
 namespace Memory {
 
 namespace {
diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h
index cba8a0714f..2e2cae17d5 100644
--- a/src/core/hle/kernel/memory.h
+++ b/src/core/hle/kernel/memory.h
@@ -4,10 +4,27 @@
 
 #pragma once
 
+#include <memory>
+
 #include "common/common_types.h"
 
+#include "core/hle/kernel/process.h"
+
 namespace Kernel {
+
 class VMManager;
+
+struct MemoryRegionInfo {
+    u32 base; // Not an address, but offset from start of FCRAM
+    u32 size;
+
+    std::shared_ptr<std::vector<u8>> linear_heap_memory;
+};
+
+void MemoryInit(u32 mem_type);
+void MemoryShutdown();
+MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
+
 }
 
 namespace Memory {
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 2cd1cfc145..1f45e6cf83 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -96,7 +96,7 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
 
             int minor = kernel_version & 0xFF;
             int major = (kernel_version >> 8) & 0xFF;
-            LOG_DEBUG(Loader, "ExHeader kernel version: %d.%d", major, minor);
+            LOG_INFO(Loader, "ExHeader kernel version: %d.%d", major, minor);
         } else {
             LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x%08X", descriptor);
         }
@@ -104,6 +104,8 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) {
 }
 
 void Process::Run(s32 main_thread_priority, u32 stack_size) {
+    memory_region = GetMemoryRegion(flags.memory_region);
+
     auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, MemoryState memory_state) {
         auto vma = vm_manager.MapMemoryBlock(segment.addr, codeset->memory,
                 segment.offset, segment.size, memory_state).Unwrap();
@@ -124,6 +126,15 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
     Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority);
 }
 
+VAddr Process::GetLinearHeapBase() const {
+    return (kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_SIZE)
+            + memory_region->base;
+}
+
+VAddr Process::GetLinearHeapLimit() const {
+    return GetLinearHeapBase() + memory_region->size;
+}
+
 ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission perms) {
     if (target < Memory::HEAP_VADDR || target + size > Memory::HEAP_VADDR_END || target + size < target) {
         return ERR_INVALID_ADDRESS;
@@ -166,19 +177,16 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
 }
 
 ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission perms) {
-    if (linear_heap_memory == nullptr) {
-        // Initialize heap
-        linear_heap_memory = std::make_shared<std::vector<u8>>();
-    }
+    auto& linheap_memory = memory_region->linear_heap_memory;
 
-    VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size();
+    VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size();
     // Games and homebrew only ever seem to pass 0 here (which lets the kernel decide the address),
     // but explicit addresses are also accepted and respected.
     if (target == 0) {
         target = heap_end;
     }
 
-    if (target < Memory::LINEAR_HEAP_VADDR || target + size > Memory::LINEAR_HEAP_VADDR_END ||
+    if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
         target > heap_end || target + size < target) {
 
         return ERR_INVALID_ADDRESS;
@@ -188,25 +196,29 @@ ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission p
     // end. It's possible to free gaps in the middle of the heap and then reallocate them later,
     // but expansions are only allowed at the end.
     if (target == heap_end) {
-        linear_heap_memory->insert(linear_heap_memory->end(), size, 0);
-        vm_manager.RefreshMemoryBlockMappings(linear_heap_memory.get());
+        linheap_memory->insert(linheap_memory->end(), size, 0);
+        vm_manager.RefreshMemoryBlockMappings(linheap_memory.get());
     }
 
-    size_t offset = target - Memory::LINEAR_HEAP_VADDR;
-    CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linear_heap_memory, offset, size, MemoryState::Continuous));
+    // TODO(yuriks): As is, this lets processes map memory allocated by other processes from the
+    // same region. It is unknown if or how the 3DS kernel checks against this.
+    size_t offset = target - GetLinearHeapBase();
+    CASCADE_RESULT(auto vma, vm_manager.MapMemoryBlock(target, linheap_memory, offset, size, MemoryState::Continuous));
     vm_manager.Reprotect(vma, perms);
 
     return MakeResult<VAddr>(target);
 }
 
 ResultCode Process::LinearFree(VAddr target, u32 size) {
-    if (linear_heap_memory == nullptr || target < Memory::LINEAR_HEAP_VADDR ||
-        target + size > Memory::LINEAR_HEAP_VADDR_END || target + size < target) {
+    auto& linheap_memory = memory_region->linear_heap_memory;
+
+    if (target < GetLinearHeapBase() || target + size > GetLinearHeapLimit() ||
+        target + size < target) {
 
         return ERR_INVALID_ADDRESS;
     }
 
-    VAddr heap_end = Memory::LINEAR_HEAP_VADDR + (u32)linear_heap_memory->size();
+    VAddr heap_end = GetLinearHeapBase() + (u32)linheap_memory->size();
     if (target + size > heap_end) {
         return ERR_INVALID_ADDRESS_STATE;
     }
@@ -221,8 +233,8 @@ ResultCode Process::LinearFree(VAddr target, u32 size) {
         ASSERT(vma != vm_manager.vma_map.end());
         ASSERT(vma->second.type == VMAType::Free);
         VAddr new_end = vma->second.base;
-        if (new_end >= Memory::LINEAR_HEAP_VADDR) {
-            linear_heap_memory->resize(new_end - Memory::LINEAR_HEAP_VADDR);
+        if (new_end >= GetLinearHeapBase()) {
+            linheap_memory->resize(new_end - GetLinearHeapBase());
         }
     }
 
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 5c7de90448..7c3a78b9e0 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -49,6 +49,7 @@ union ProcessFlags {
 };
 
 class ResourceLimit;
+struct MemoryRegionInfo;
 
 struct CodeSet final : public Object {
     static SharedPtr<CodeSet> Create(std::string name, u64 program_id);
@@ -135,11 +136,14 @@ public:
     // The left/right bounds of the address space covered by heap_memory.
     VAddr heap_start = 0, heap_end = 0;
 
-    std::shared_ptr<std::vector<u8>> linear_heap_memory;
+    MemoryRegionInfo* memory_region = nullptr;
 
     /// Bitmask of the used TLS slots
     std::bitset<300> used_tls_slots;
 
+    VAddr GetLinearHeapBase() const;
+    VAddr GetLinearHeapLimit() const;
+
     ResultVal<VAddr> HeapAllocate(VAddr target, u32 size, VMAPermission perms);
     ResultCode HeapFree(VAddr target, u32 size);
 
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index b944f4af08..e1a416def1 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -102,7 +102,7 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
         if (addr0 >= Memory::HEAP_VADDR && addr0 < Memory::HEAP_VADDR_END) {
             ResultCode result = process.HeapFree(addr0, size);
             if (result.IsError()) return result;
-        } else if (addr0 >= Memory::LINEAR_HEAP_VADDR && addr0 < Memory::LINEAR_HEAP_VADDR_END) {
+        } else if (addr0 >= process.GetLinearHeapBase() && addr0 < process.GetLinearHeapLimit()) {
             ResultCode result = process.LinearFree(addr0, size);
             if (result.IsError()) return result;
         } else {
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 935dac90f7..cde390b8a4 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -9,6 +9,7 @@
 #include "common/logging/log.h"
 #include "common/swap.h"
 
+#include "core/hle/kernel/process.h"
 #include "core/memory.h"
 #include "core/memory_setup.h"
 
@@ -208,6 +209,8 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) {
         return addr - DSP_RAM_VADDR + DSP_RAM_PADDR;
     } else if (addr >= IO_AREA_VADDR && addr < IO_AREA_VADDR_END) {
         return addr - IO_AREA_VADDR + IO_AREA_PADDR;
+    } else if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) {
+        return addr - NEW_LINEAR_HEAP_VADDR + FCRAM_PADDR;
     }
 
     LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
@@ -221,7 +224,7 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) {
     } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
         return addr - VRAM_PADDR + VRAM_VADDR;
     } else if (addr >= FCRAM_PADDR && addr < FCRAM_PADDR_END) {
-        return addr - FCRAM_PADDR + LINEAR_HEAP_VADDR;
+        return addr - FCRAM_PADDR + Kernel::g_current_process->GetLinearHeapBase();
     } else if (addr >= DSP_RAM_PADDR && addr < DSP_RAM_PADDR_END) {
         return addr - DSP_RAM_PADDR + DSP_RAM_VADDR;
     } else if (addr >= IO_AREA_PADDR && addr < IO_AREA_PADDR_END) {
diff --git a/src/core/memory.h b/src/core/memory.h
index e6da3e2a55..d1d32f0ddc 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -107,6 +107,11 @@ enum : VAddr {
     TLS_AREA_VADDR     = 0x1FF82000,
     TLS_AREA_SIZE      = 0x00030000, // Each TLS buffer is 0x200 bytes, allows for 300 threads
     TLS_AREA_VADDR_END = TLS_AREA_VADDR + TLS_AREA_SIZE,
+
+    /// Equivalent to LINEAR_HEAP_VADDR, but expanded to cover the extra memory in the New 3DS.
+    NEW_LINEAR_HEAP_VADDR     = 0x30000000,
+    NEW_LINEAR_HEAP_SIZE      = 0x10000000,
+    NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
 };
 
 u8 Read8(VAddr addr);