diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index a5e71d8791ed4a1b1ae018382df3b6196bfecd5b..845626fc5b74ad4d0a16d14b392c0b8064b3fbb8 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -44,6 +44,7 @@ add_library(common STATIC
     detached_tasks.cpp
     detached_tasks.h
     bit_field.h
+    bit_util.h
     cityhash.cpp
     cityhash.h
     color.h
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
new file mode 100644
index 0000000000000000000000000000000000000000..1eea17ba13d8e09818699bbc9e183087c4ac3a9d
--- /dev/null
+++ b/src/common/bit_util.h
@@ -0,0 +1,61 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <climits>
+#include <cstddef>
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#include "common/common_types.h"
+
+namespace Common {
+
+/// Gets the size of a specified type T in bits.
+template <typename T>
+constexpr std::size_t BitSize() {
+    return sizeof(T) * CHAR_BIT;
+}
+
+#ifdef _MSC_VER
+inline u32 CountLeadingZeroes32(u32 value) {
+    unsigned long leading_zero = 0;
+
+    if (_BitScanReverse(&leading_zero, value) != 0) {
+        return 31 - leading_zero;
+    }
+
+    return 32;
+}
+
+inline u64 CountLeadingZeroes64(u64 value) {
+    unsigned long leading_zero = 0;
+
+    if (_BitScanReverse64(&leading_zero, value) != 0) {
+        return 63 - leading_zero;
+    }
+
+    return 64;
+}
+#else
+inline u32 CountLeadingZeroes32(u32 value) {
+    if (value == 0) {
+        return 32;
+    }
+
+    return __builtin_clz(value);
+}
+
+inline u64 CountLeadingZeroes64(u64 value) {
+    if (value == 0) {
+        return 64;
+    }
+
+    return __builtin_clzll(value);
+}
+#endif
+} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 93f5ba3fe95c4b4108bb442f29ce4250e5f838f5..f38271336c0b7583c0938e1304ce27c5febcc3bf 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -115,6 +115,8 @@ add_library(core STATIC
     hle/kernel/object.h
     hle/kernel/process.cpp
     hle/kernel/process.h
+    hle/kernel/process_capability.cpp
+    hle/kernel/process_capability.h
     hle/kernel/readable_event.cpp
     hle/kernel/readable_event.h
     hle/kernel/resource_limit.cpp
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 8903ed1d3f08d85b4f80a6a44ff382536da27cfa..e90c8c2de0519d5266f005076c3bb24b0b77cacd 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -40,6 +40,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
     if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
         return Loader::ResultStatus::ErrorBadFileAccessHeader;
 
+    aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
+    const u64 read_size = aci_header.kac_size;
+    const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset;
+    if (file->ReadBytes(aci_kernel_capabilities.data(), read_size, read_offset) != read_size) {
+        return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors;
+    }
+
     return Loader::ResultStatus::Success;
 }
 
@@ -71,6 +78,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
     return aci_file_access.permissions;
 }
 
+const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
+    return aci_kernel_capabilities;
+}
+
 void ProgramMetadata::Print() const {
     LOG_DEBUG(Service_FS, "Magic:                  {:.4}", npdm_header.magic.data());
     LOG_DEBUG(Service_FS, "Main thread priority:   0x{:02X}", npdm_header.main_thread_priority);
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index e4470d6f086d73937d8ee621892ea8575beb1804..0033ba3475edc55d0ce99f7b608856ddeb2efae2 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <vector>
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -38,6 +39,8 @@ enum class ProgramFilePermission : u64 {
  */
 class ProgramMetadata {
 public:
+    using KernelCapabilityDescriptors = std::vector<u32>;
+
     ProgramMetadata();
     ~ProgramMetadata();
 
@@ -50,6 +53,7 @@ public:
     u32 GetMainThreadStackSize() const;
     u64 GetTitleID() const;
     u64 GetFilesystemPermissions() const;
+    const KernelCapabilityDescriptors& GetKernelCapabilities() const;
 
     void Print() const;
 
@@ -154,6 +158,8 @@ private:
 
     FileAccessControl acid_file_access;
     FileAccessHeader aci_file_access;
+
+    KernelCapabilityDescriptors aci_kernel_capabilities;
 };
 
 } // namespace FileSys
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index d8240ec6d0e283e5d2ae6e56b5470021df290c8b..d17eb0cb6f1b6cef210689e7af802ecac62c305c 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -11,6 +11,7 @@ namespace Kernel {
 // Confirmed Switch kernel error codes
 
 constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
+constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
 constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
 constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
 constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
@@ -30,6 +31,7 @@ constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121};
 constexpr ResultCode ERR_BUSY{ErrorModule::Kernel, 122};
 constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123};
 constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125};
+constexpr ResultCode ERR_RESERVED_VALUE{ErrorModule::Kernel, 126};
 constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132};
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index 6b7927fd83a8cfb4a7015aa5ac120367a2a3c9cf..89a3bc740345f5a7f78bf85553162dce96d10f30 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -43,6 +43,9 @@ enum KernelHandle : Handle {
  */
 class HandleTable final : NonCopyable {
 public:
+    /// This is the maximum limit of handles allowed per process in Horizon
+    static constexpr std::size_t MAX_COUNT = 1024;
+
     HandleTable();
     ~HandleTable();
 
@@ -91,9 +94,6 @@ public:
     void Clear();
 
 private:
-    /// This is the maximum limit of handles allowed per process in Horizon
-    static constexpr std::size_t MAX_COUNT = 1024;
-
     /// Stores the Object referenced by the handle or null if the slot is empty.
     std::array<SharedPtr<Object>, MAX_COUNT> objects;
 
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 5356a4a3f68c42f9eb95056cdfee80b1e9aa5f5e..4f209a979f863d383958d4c172aa2768639c9f08 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -28,13 +28,11 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
     SharedPtr<Process> process(new Process(kernel));
 
     process->name = std::move(name);
-    process->flags.raw = 0;
-    process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
     process->resource_limit = kernel.GetSystemResourceLimit();
     process->status = ProcessStatus::Created;
     process->program_id = 0;
     process->process_id = kernel.CreateNewProcessID();
-    process->svc_access_mask.set();
+    process->capabilities.InitializeForMetadatalessProcess();
 
     std::mt19937 rng(Settings::values.rng_seed.value_or(0));
     std::uniform_int_distribution<u64> distribution;
@@ -64,83 +62,15 @@ ResultCode Process::ClearSignalState() {
     return RESULT_SUCCESS;
 }
 
-void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
+ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
     program_id = metadata.GetTitleID();
     ideal_processor = metadata.GetMainThreadCore();
     is_64bit_process = metadata.Is64BitProgram();
-    vm_manager.Reset(metadata.GetAddressSpaceType());
-}
-
-void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) {
-    for (std::size_t i = 0; i < len; ++i) {
-        u32 descriptor = kernel_caps[i];
-        u32 type = descriptor >> 20;
-
-        if (descriptor == 0xFFFFFFFF) {
-            // Unused descriptor entry
-            continue;
-        } else if ((type & 0xF00) == 0xE00) { // 0x0FFF
-            // Allowed interrupts list
-            LOG_WARNING(Loader, "ExHeader allowed interrupts list ignored");
-        } else if ((type & 0xF80) == 0xF00) { // 0x07FF
-            // Allowed syscalls mask
-            unsigned int index = ((descriptor >> 24) & 7) * 24;
-            u32 bits = descriptor & 0xFFFFFF;
-
-            while (bits && index < svc_access_mask.size()) {
-                svc_access_mask.set(index, bits & 1);
-                ++index;
-                bits >>= 1;
-            }
-        } else if ((type & 0xFF0) == 0xFE0) { // 0x00FF
-            // Handle table size
-            handle_table_size = descriptor & 0x3FF;
-        } else if ((type & 0xFF8) == 0xFF0) { // 0x007F
-            // Misc. flags
-            flags.raw = descriptor & 0xFFFF;
-        } else if ((type & 0xFFE) == 0xFF8) { // 0x001F
-            // Mapped memory range
-            if (i + 1 >= len || ((kernel_caps[i + 1] >> 20) & 0xFFE) != 0xFF8) {
-                LOG_WARNING(Loader, "Incomplete exheader memory range descriptor ignored.");
-                continue;
-            }
-            u32 end_desc = kernel_caps[i + 1];
-            ++i; // Skip over the second descriptor on the next iteration
 
-            AddressMapping mapping;
-            mapping.address = descriptor << 12;
-            VAddr end_address = end_desc << 12;
-
-            if (mapping.address < end_address) {
-                mapping.size = end_address - mapping.address;
-            } else {
-                mapping.size = 0;
-            }
+    vm_manager.Reset(metadata.GetAddressSpaceType());
 
-            mapping.read_only = (descriptor & (1 << 20)) != 0;
-            mapping.unk_flag = (end_desc & (1 << 20)) != 0;
-
-            address_mappings.push_back(mapping);
-        } else if ((type & 0xFFF) == 0xFFE) { // 0x000F
-            // Mapped memory page
-            AddressMapping mapping;
-            mapping.address = descriptor << 12;
-            mapping.size = Memory::PAGE_SIZE;
-            mapping.read_only = false;
-            mapping.unk_flag = false;
-
-            address_mappings.push_back(mapping);
-        } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF
-            // Kernel version
-            kernel_version = descriptor & 0xFFFF;
-
-            int minor = kernel_version & 0xFF;
-            int major = (kernel_version >> 8) & 0xFF;
-            LOG_INFO(Loader, "ExHeader kernel version: {}.{}", major, minor);
-        } else {
-            LOG_ERROR(Loader, "Unhandled kernel caps descriptor: 0x{:08X}", descriptor);
-        }
-    }
+    const auto& caps = metadata.GetKernelCapabilities();
+    return capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager);
 }
 
 void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 7da367251f6be0666c991b7ec90f54cdbee44111..2c0b20f9ebaaa82204f419f977d33ae8d33eb006 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -11,9 +11,9 @@
 #include <string>
 #include <vector>
 #include <boost/container/static_vector.hpp>
-#include "common/bit_field.h"
 #include "common/common_types.h"
 #include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/process_capability.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/vm_manager.h"
 #include "core/hle/kernel/wait_object.h"
@@ -42,24 +42,6 @@ enum class MemoryRegion : u16 {
     BASE = 3,
 };
 
-union ProcessFlags {
-    u16 raw;
-
-    BitField<0, 1, u16>
-        allow_debug; ///< Allows other processes to attach to and debug this process.
-    BitField<1, 1, u16> force_debug; ///< Allows this process to attach to processes even if they
-                                     /// don't have allow_debug set.
-    BitField<2, 1, u16> allow_nonalphanum;
-    BitField<3, 1, u16> shared_page_writable; ///< Shared page is mapped with write permissions.
-    BitField<4, 1, u16> privileged_priority;  ///< Can use priority levels higher than 24.
-    BitField<5, 1, u16> allow_main_args;
-    BitField<6, 1, u16> shared_device_mem;
-    BitField<7, 1, u16> runnable_on_sleep;
-    BitField<8, 4, MemoryRegion>
-        memory_region;                ///< Default region for memory allocations for this process
-    BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
-};
-
 /**
  * Indicates the status of a Process instance.
  *
@@ -192,13 +174,13 @@ public:
     }
 
     /// Gets the bitmask of allowed CPUs that this process' threads can run on.
-    u32 GetAllowedProcessorMask() const {
-        return allowed_processor_mask;
+    u64 GetAllowedProcessorMask() const {
+        return capabilities.GetCoreMask();
     }
 
     /// Gets the bitmask of allowed thread priorities.
-    u32 GetAllowedThreadPriorityMask() const {
-        return allowed_thread_priority_mask;
+    u64 GetAllowedThreadPriorityMask() const {
+        return capabilities.GetPriorityMask();
     }
 
     u32 IsVirtualMemoryEnabled() const {
@@ -239,15 +221,12 @@ public:
      * Loads process-specifics configuration info with metadata provided
      * by an executable.
      *
-     * @param metadata The provided metadata to load process specific info.
-     */
-    void LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
-
-    /**
-     * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them
-     * to this process.
+     * @param metadata The provided metadata to load process specific info from.
+     *
+     * @returns RESULT_SUCCESS if all relevant metadata was able to be
+     *          loaded and parsed. Otherwise, an error code is returned.
      */
-    void ParseKernelCaps(const u32* kernel_caps, std::size_t len);
+    ResultCode LoadFromMetadata(const FileSys::ProgramMetadata& metadata);
 
     /**
      * Applies address space changes and launches the process main thread.
@@ -308,22 +287,8 @@ private:
     /// Resource limit descriptor for this process
     SharedPtr<ResourceLimit> resource_limit;
 
-    /// The process may only call SVCs which have the corresponding bit set.
-    std::bitset<0x80> svc_access_mask;
-    /// Maximum size of the handle table for the process.
-    u32 handle_table_size = 0x200;
-    /// Special memory ranges mapped into this processes address space. This is used to give
-    /// processes access to specific I/O regions and device memory.
-    boost::container::static_vector<AddressMapping, 8> address_mappings;
-    ProcessFlags flags;
-    /// Kernel compatibility version for this process
-    u16 kernel_version = 0;
     /// The default CPU for this process, threads are scheduled on this cpu by default.
     u8 ideal_processor = 0;
-    /// Bitmask of allowed CPUs that this process' threads can run on. TODO(Subv): Actually parse
-    /// this value from the process header.
-    u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK;
-    u32 allowed_thread_priority_mask = 0xFFFFFFFF;
     u32 is_virtual_address_memory_enabled = 0;
 
     /// The Thread Local Storage area is allocated as processes create threads,
@@ -333,6 +298,9 @@ private:
     /// This vector will grow as more pages are allocated for new threads.
     std::vector<std::bitset<8>> tls_slots;
 
+    /// Contains the parsed process capability descriptors.
+    ProcessCapabilities capabilities;
+
     /// Whether or not this process is AArch64, or AArch32.
     /// By default, we currently assume this is true, unless otherwise
     /// specified by metadata provided to the process during loading.
diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3a2164b25ab0a846865ee059a4b7c1dee3b1ffd7
--- /dev/null
+++ b/src/core/hle/kernel/process_capability.cpp
@@ -0,0 +1,355 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_util.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/process_capability.h"
+#include "core/hle/kernel/vm_manager.h"
+
+namespace Kernel {
+namespace {
+
+// clang-format off
+
+// Shift offsets for kernel capability types.
+enum : u32 {
+    CapabilityOffset_PriorityAndCoreNum = 3,
+    CapabilityOffset_Syscall            = 4,
+    CapabilityOffset_MapPhysical        = 6,
+    CapabilityOffset_MapIO              = 7,
+    CapabilityOffset_Interrupt          = 11,
+    CapabilityOffset_ProgramType        = 13,
+    CapabilityOffset_KernelVersion      = 14,
+    CapabilityOffset_HandleTableSize    = 15,
+    CapabilityOffset_Debug              = 16,
+};
+
+// Combined mask of all parameters that may be initialized only once.
+constexpr u32 InitializeOnceMask = (1U << CapabilityOffset_PriorityAndCoreNum) |
+                                   (1U << CapabilityOffset_ProgramType) |
+                                   (1U << CapabilityOffset_KernelVersion) |
+                                   (1U << CapabilityOffset_HandleTableSize) |
+                                   (1U << CapabilityOffset_Debug);
+
+// Packed kernel version indicating 10.4.0
+constexpr u32 PackedKernelVersion = 0x520000;
+
+// Indicates possible types of capabilities that can be specified.
+enum class CapabilityType : u32 {
+    Unset              = 0U,
+    PriorityAndCoreNum = (1U << CapabilityOffset_PriorityAndCoreNum) - 1,
+    Syscall            = (1U << CapabilityOffset_Syscall) - 1,
+    MapPhysical        = (1U << CapabilityOffset_MapPhysical) - 1,
+    MapIO              = (1U << CapabilityOffset_MapIO) - 1,
+    Interrupt          = (1U << CapabilityOffset_Interrupt) - 1,
+    ProgramType        = (1U << CapabilityOffset_ProgramType) - 1,
+    KernelVersion      = (1U << CapabilityOffset_KernelVersion) - 1,
+    HandleTableSize    = (1U << CapabilityOffset_HandleTableSize) - 1,
+    Debug              = (1U << CapabilityOffset_Debug) - 1,
+    Ignorable          = 0xFFFFFFFFU,
+};
+
+// clang-format on
+
+constexpr CapabilityType GetCapabilityType(u32 value) {
+    return static_cast<CapabilityType>((~value & (value + 1)) - 1);
+}
+
+u32 GetFlagBitOffset(CapabilityType type) {
+    const auto value = static_cast<u32>(type);
+    return static_cast<u32>(Common::BitSize<u32>() - Common::CountLeadingZeroes32(value));
+}
+
+} // Anonymous namespace
+
+ResultCode ProcessCapabilities::InitializeForKernelProcess(const u32* capabilities,
+                                                           std::size_t num_capabilities,
+                                                           VMManager& vm_manager) {
+    Clear();
+
+    // Allow all cores and priorities.
+    core_mask = 0xF;
+    priority_mask = 0xFFFFFFFFFFFFFFFF;
+    kernel_version = PackedKernelVersion;
+
+    return ParseCapabilities(capabilities, num_capabilities, vm_manager);
+}
+
+ResultCode ProcessCapabilities::InitializeForUserProcess(const u32* capabilities,
+                                                         std::size_t num_capabilities,
+                                                         VMManager& vm_manager) {
+    Clear();
+
+    return ParseCapabilities(capabilities, num_capabilities, vm_manager);
+}
+
+void ProcessCapabilities::InitializeForMetadatalessProcess() {
+    // Allow all cores and priorities
+    core_mask = 0xF;
+    priority_mask = 0xFFFFFFFFFFFFFFFF;
+    kernel_version = PackedKernelVersion;
+
+    // Allow all system calls and interrupts.
+    svc_capabilities.set();
+    interrupt_capabilities.set();
+
+    // Allow using the maximum possible amount of handles
+    handle_table_size = static_cast<u32>(HandleTable::MAX_COUNT);
+
+    // Allow all debugging capabilities.
+    is_debuggable = true;
+    can_force_debug = true;
+}
+
+ResultCode ProcessCapabilities::ParseCapabilities(const u32* capabilities,
+                                                  std::size_t num_capabilities,
+                                                  VMManager& vm_manager) {
+    u32 set_flags = 0;
+    u32 set_svc_bits = 0;
+
+    for (std::size_t i = 0; i < num_capabilities; ++i) {
+        const u32 descriptor = capabilities[i];
+        const auto type = GetCapabilityType(descriptor);
+
+        if (type == CapabilityType::MapPhysical) {
+            i++;
+
+            // The MapPhysical type uses two descriptor flags for its parameters.
+            // If there's only one, then there's a problem.
+            if (i >= num_capabilities) {
+                return ERR_INVALID_COMBINATION;
+            }
+
+            const auto size_flags = capabilities[i];
+            if (GetCapabilityType(size_flags) != CapabilityType::MapPhysical) {
+                return ERR_INVALID_COMBINATION;
+            }
+
+            const auto result = HandleMapPhysicalFlags(descriptor, size_flags, vm_manager);
+            if (result.IsError()) {
+                return result;
+            }
+        } else {
+            const auto result =
+                ParseSingleFlagCapability(set_flags, set_svc_bits, descriptor, vm_manager);
+            if (result.IsError()) {
+                return result;
+            }
+        }
+    }
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits,
+                                                          u32 flag, VMManager& vm_manager) {
+    const auto type = GetCapabilityType(flag);
+
+    if (type == CapabilityType::Unset) {
+        return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+    }
+
+    // Bail early on ignorable entries, as one would expect,
+    // ignorable descriptors can be ignored.
+    if (type == CapabilityType::Ignorable) {
+        return RESULT_SUCCESS;
+    }
+
+    // Ensure that the give flag hasn't already been initialized before.
+    // If it has been, then bail.
+    const u32 flag_length = GetFlagBitOffset(type);
+    const u32 set_flag = 1U << flag_length;
+    if ((set_flag & set_flags & InitializeOnceMask) != 0) {
+        return ERR_INVALID_COMBINATION;
+    }
+    set_flags |= set_flag;
+
+    switch (type) {
+    case CapabilityType::PriorityAndCoreNum:
+        return HandlePriorityCoreNumFlags(flag);
+    case CapabilityType::Syscall:
+        return HandleSyscallFlags(set_svc_bits, flag);
+    case CapabilityType::MapIO:
+        return HandleMapIOFlags(flag, vm_manager);
+    case CapabilityType::Interrupt:
+        return HandleInterruptFlags(flag);
+    case CapabilityType::ProgramType:
+        return HandleProgramTypeFlags(flag);
+    case CapabilityType::KernelVersion:
+        return HandleKernelVersionFlags(flag);
+    case CapabilityType::HandleTableSize:
+        return HandleHandleTableFlags(flag);
+    case CapabilityType::Debug:
+        return HandleDebugFlags(flag);
+    default:
+        break;
+    }
+
+    return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+}
+
+void ProcessCapabilities::Clear() {
+    svc_capabilities.reset();
+    interrupt_capabilities.reset();
+
+    core_mask = 0;
+    priority_mask = 0;
+
+    handle_table_size = 0;
+    kernel_version = 0;
+
+    program_type = ProgramType::SysModule;
+
+    is_debuggable = false;
+    can_force_debug = false;
+}
+
+ResultCode ProcessCapabilities::HandlePriorityCoreNumFlags(u32 flags) {
+    if (priority_mask != 0 || core_mask != 0) {
+        return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+    }
+
+    const u32 core_num_min = (flags >> 16) & 0xFF;
+    const u32 core_num_max = (flags >> 24) & 0xFF;
+    if (core_num_min > core_num_max) {
+        return ERR_INVALID_COMBINATION;
+    }
+
+    const u32 priority_min = (flags >> 10) & 0x3F;
+    const u32 priority_max = (flags >> 4) & 0x3F;
+    if (priority_min > priority_max) {
+        return ERR_INVALID_COMBINATION;
+    }
+
+    // The switch only has 4 usable cores.
+    if (core_num_max >= 4) {
+        return ERR_INVALID_PROCESSOR_ID;
+    }
+
+    const auto make_mask = [](u64 min, u64 max) {
+        const u64 range = max - min + 1;
+        const u64 mask = (1ULL << range) - 1;
+
+        return mask << min;
+    };
+
+    core_mask = make_mask(core_num_min, core_num_max);
+    priority_mask = make_mask(priority_min, priority_max);
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleSyscallFlags(u32& set_svc_bits, u32 flags) {
+    const u32 index = flags >> 29;
+    const u32 svc_bit = 1U << index;
+
+    // If we've already set this svc before, bail.
+    if ((set_svc_bits & svc_bit) != 0) {
+        return ERR_INVALID_COMBINATION;
+    }
+    set_svc_bits |= svc_bit;
+
+    const u32 svc_mask = (flags >> 5) & 0xFFFFFF;
+    for (u32 i = 0; i < 24; ++i) {
+        const u32 svc_number = index * 24 + i;
+
+        if ((svc_mask & (1U << i)) == 0) {
+            continue;
+        }
+
+        if (svc_number >= svc_capabilities.size()) {
+            return ERR_OUT_OF_RANGE;
+        }
+
+        svc_capabilities[svc_number] = true;
+    }
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleMapPhysicalFlags(u32 flags, u32 size_flags,
+                                                       VMManager& vm_manager) {
+    // TODO(Lioncache): Implement once the memory manager can handle this.
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleMapIOFlags(u32 flags, VMManager& vm_manager) {
+    // TODO(Lioncache): Implement once the memory manager can handle this.
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleInterruptFlags(u32 flags) {
+    constexpr u32 interrupt_ignore_value = 0x3FF;
+    const u32 interrupt0 = (flags >> 12) & 0x3FF;
+    const u32 interrupt1 = (flags >> 22) & 0x3FF;
+
+    for (u32 interrupt : {interrupt0, interrupt1}) {
+        if (interrupt == interrupt_ignore_value) {
+            continue;
+        }
+
+        // NOTE:
+        // This should be checking a generic interrupt controller value
+        // as part of the calculation, however, given we don't currently
+        // emulate that, it's sufficient to mark every interrupt as defined.
+
+        if (interrupt >= interrupt_capabilities.size()) {
+            return ERR_OUT_OF_RANGE;
+        }
+
+        interrupt_capabilities[interrupt] = true;
+    }
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleProgramTypeFlags(u32 flags) {
+    const u32 reserved = flags >> 17;
+    if (reserved != 0) {
+        return ERR_RESERVED_VALUE;
+    }
+
+    program_type = static_cast<ProgramType>((flags >> 14) & 0b111);
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleKernelVersionFlags(u32 flags) {
+    // Yes, the internal member variable is checked in the actual kernel here.
+    // This might look odd for options that are only allowed to be initialized
+    // just once, however the kernel has a separate initialization function for
+    // kernel processes and userland processes. The kernel variant sets this
+    // member variable ahead of time.
+
+    const u32 major_version = kernel_version >> 19;
+
+    if (major_version != 0 || flags < 0x80000) {
+        return ERR_INVALID_CAPABILITY_DESCRIPTOR;
+    }
+
+    kernel_version = flags;
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
+    const u32 reserved = flags >> 26;
+    if (reserved != 0) {
+        return ERR_RESERVED_VALUE;
+    }
+
+    handle_table_size = (flags >> 16) & 0x3FF;
+    return RESULT_SUCCESS;
+}
+
+ResultCode ProcessCapabilities::HandleDebugFlags(u32 flags) {
+    const u32 reserved = flags >> 19;
+    if (reserved != 0) {
+        return ERR_RESERVED_VALUE;
+    }
+
+    is_debuggable = (flags & 0x20000) != 0;
+    can_force_debug = (flags & 0x40000) != 0;
+    return RESULT_SUCCESS;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/process_capability.h b/src/core/hle/kernel/process_capability.h
new file mode 100644
index 0000000000000000000000000000000000000000..fbc8812a3fd64fde24db33bfca38ec2063e48cb8
--- /dev/null
+++ b/src/core/hle/kernel/process_capability.h
@@ -0,0 +1,264 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <bitset>
+
+#include "common/common_types.h"
+
+union ResultCode;
+
+namespace Kernel {
+
+class VMManager;
+
+/// The possible types of programs that may be indicated
+/// by the program type capability descriptor.
+enum class ProgramType {
+    SysModule,
+    Application,
+    Applet,
+};
+
+/// Handles kernel capability descriptors that are provided by
+/// application metadata. These descriptors provide information
+/// that alters certain parameters for kernel process instance
+/// that will run said application (or applet).
+///
+/// Capabilities are a sequence of flag descriptors, that indicate various
+/// configurations and constraints for a particular process.
+///
+/// Flag types are indicated by a sequence of set low bits. E.g. the
+/// types are indicated with the low bits as follows (where x indicates "don't care"):
+///
+/// - Priority and core mask   : 0bxxxxxxxxxxxx0111
+/// - Allowed service call mask: 0bxxxxxxxxxxx01111
+/// - Map physical memory      : 0bxxxxxxxxx0111111
+/// - Map IO memory            : 0bxxxxxxxx01111111
+/// - Interrupts               : 0bxxxx011111111111
+/// - Application type         : 0bxx01111111111111
+/// - Kernel version           : 0bx011111111111111
+/// - Handle table size        : 0b0111111111111111
+/// - Debugger flags           : 0b1111111111111111
+///
+/// These are essentially a bit offset subtracted by 1 to create a mask.
+/// e.g. The first entry in the above list is simply bit 3 (value 8 -> 0b1000)
+///      subtracted by one (7 -> 0b0111)
+///
+/// An example of a bit layout (using the map physical layout):
+/// <example>
+///   The MapPhysical type indicates a sequence entry pair of:
+///
+///   [initial, memory_flags], where:
+///
+///   initial:
+///     bits:
+///       7-24: Starting page to map memory at.
+///       25  : Indicates if the memory should be mapped as read only.
+///
+///   memory_flags:
+///     bits:
+///       7-20 : Number of pages to map
+///       21-25: Seems to be reserved (still checked against though)
+///       26   : Whether or not the memory being mapped is IO memory, or physical memory
+/// </example>
+///
+class ProcessCapabilities {
+public:
+    using InterruptCapabilities = std::bitset<1024>;
+    using SyscallCapabilities = std::bitset<128>;
+
+    ProcessCapabilities() = default;
+    ProcessCapabilities(const ProcessCapabilities&) = delete;
+    ProcessCapabilities(ProcessCapabilities&&) = default;
+
+    ProcessCapabilities& operator=(const ProcessCapabilities&) = delete;
+    ProcessCapabilities& operator=(ProcessCapabilities&&) = default;
+
+    /// Initializes this process capabilities instance for a kernel process.
+    ///
+    /// @param capabilities     The capabilities to parse
+    /// @param num_capabilities The number of capabilities to parse.
+    /// @param vm_manager       The memory manager to use for handling any mapping-related
+    ///                         operations (such as mapping IO memory, etc).
+    ///
+    /// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
+    ///          otherwise, an error code upon failure.
+    ///
+    ResultCode InitializeForKernelProcess(const u32* capabilities, std::size_t num_capabilities,
+                                          VMManager& vm_manager);
+
+    /// Initializes this process capabilities instance for a userland process.
+    ///
+    /// @param capabilities     The capabilities to parse.
+    /// @param num_capabilities The total number of capabilities to parse.
+    /// @param vm_manager       The memory manager to use for handling any mapping-related
+    ///                         operations (such as mapping IO memory, etc).
+    ///
+    /// @returns RESULT_SUCCESS if this capabilities instance was able to be initialized,
+    ///          otherwise, an error code upon failure.
+    ///
+    ResultCode InitializeForUserProcess(const u32* capabilities, std::size_t num_capabilities,
+                                        VMManager& vm_manager);
+
+    /// Initializes this process capabilities instance for a process that does not
+    /// have any metadata to parse.
+    ///
+    /// This is necessary, as we allow running raw executables, and the internal
+    /// kernel process capabilities also determine what CPU cores the process is
+    /// allowed to run on, and what priorities are allowed for  threads. It also
+    /// determines the max handle table size, what the program type is, whether or
+    /// not the process can be debugged, or whether it's possible for a process to
+    /// forcibly debug another process.
+    ///
+    /// Given the above, this essentially enables all capabilities across the board
+    /// for the process. It allows the process to:
+    ///
+    /// - Run on any core
+    /// - Use any thread priority
+    /// - Use the maximum amount of handles a process is allowed to.
+    /// - Be debuggable
+    /// - Forcibly debug other processes.
+    ///
+    /// Note that this is not a behavior that the kernel allows a process to do via
+    /// a single function like this. This is yuzu-specific behavior to handle
+    /// executables with no capability descriptors whatsoever to derive behavior from.
+    /// It being yuzu-specific is why this is also not the default behavior and not
+    /// done by default in the constructor.
+    ///
+    void InitializeForMetadatalessProcess();
+
+    /// Gets the allowable core mask
+    u64 GetCoreMask() const {
+        return core_mask;
+    }
+
+    /// Gets the allowable priority mask
+    u64 GetPriorityMask() const {
+        return priority_mask;
+    }
+
+    /// Gets the SVC access permission bits
+    const SyscallCapabilities& GetServiceCapabilities() const {
+        return svc_capabilities;
+    }
+
+    /// Gets the valid interrupt bits.
+    const InterruptCapabilities& GetInterruptCapabilities() const {
+        return interrupt_capabilities;
+    }
+
+    /// Gets the program type for this process.
+    ProgramType GetProgramType() const {
+        return program_type;
+    }
+
+    /// Gets the number of total allowable handles for the process' handle table.
+    u32 GetHandleTableSize() const {
+        return handle_table_size;
+    }
+
+    /// Gets the kernel version value.
+    u32 GetKernelVersion() const {
+        return kernel_version;
+    }
+
+    /// Whether or not this process can be debugged.
+    bool IsDebuggable() const {
+        return is_debuggable;
+    }
+
+    /// Whether or not this process can forcibly debug another
+    /// process, even if that process is not considered debuggable.
+    bool CanForceDebug() const {
+        return can_force_debug;
+    }
+
+private:
+    /// Attempts to parse a given sequence of capability descriptors.
+    ///
+    /// @param capabilities     The sequence of capability descriptors to parse.
+    /// @param num_capabilities The number of descriptors within the given sequence.
+    /// @param vm_manager       The memory manager that will perform any memory
+    ///                         mapping if necessary.
+    ///
+    /// @return RESULT_SUCCESS if no errors occur, otherwise an error code.
+    ///
+    ResultCode ParseCapabilities(const u32* capabilities, std::size_t num_capabilities,
+                                 VMManager& vm_manager);
+
+    /// Attempts to parse a capability descriptor that is only represented by a
+    /// single flag set.
+    ///
+    /// @param set_flags    Running set of flags that are used to catch
+    ///                     flags being initialized more than once when they shouldn't be.
+    /// @param set_svc_bits Running set of bits representing the allowed supervisor calls mask.
+    /// @param flag         The flag to attempt to parse.
+    /// @param vm_manager   The memory manager that will perform any memory
+    ///                     mapping if necessary.
+    ///
+    /// @return RESULT_SUCCESS if no errors occurred, otherwise an error code.
+    ///
+    ResultCode ParseSingleFlagCapability(u32& set_flags, u32& set_svc_bits, u32 flag,
+                                         VMManager& vm_manager);
+
+    /// Clears the internal state of this process capability instance. Necessary,
+    /// to have a sane starting point due to us allowing running executables without
+    /// configuration metadata. We assume a process is not going to have metadata,
+    /// and if it turns out that the process does, in fact, have metadata, then
+    /// we attempt to parse it. Thus, we need this to reset data members back to
+    /// a good state.
+    ///
+    /// DO NOT ever make this a public member function. This isn't an invariant
+    /// anything external should depend upon (and if anything comes to rely on it,
+    /// you should immediately be questioning the design of that thing, not this
+    /// class. If the kernel itself can run without depending on behavior like that,
+    /// then so can yuzu).
+    ///
+    void Clear();
+
+    /// Handles flags related to the priority and core number capability flags.
+    ResultCode HandlePriorityCoreNumFlags(u32 flags);
+
+    /// Handles flags related to determining the allowable SVC mask.
+    ResultCode HandleSyscallFlags(u32& set_svc_bits, u32 flags);
+
+    /// Handles flags related to mapping physical memory pages.
+    ResultCode HandleMapPhysicalFlags(u32 flags, u32 size_flags, VMManager& vm_manager);
+
+    /// Handles flags related to mapping IO pages.
+    ResultCode HandleMapIOFlags(u32 flags, VMManager& vm_manager);
+
+    /// Handles flags related to the interrupt capability flags.
+    ResultCode HandleInterruptFlags(u32 flags);
+
+    /// Handles flags related to the program type.
+    ResultCode HandleProgramTypeFlags(u32 flags);
+
+    /// Handles flags related to the handle table size.
+    ResultCode HandleHandleTableFlags(u32 flags);
+
+    /// Handles flags related to the kernel version capability flags.
+    ResultCode HandleKernelVersionFlags(u32 flags);
+
+    /// Handles flags related to debug-specific capabilities.
+    ResultCode HandleDebugFlags(u32 flags);
+
+    SyscallCapabilities svc_capabilities;
+    InterruptCapabilities interrupt_capabilities;
+
+    u64 core_mask = 0;
+    u64 priority_mask = 0;
+
+    u32 handle_table_size = 0;
+    u32 kernel_version = 0;
+
+    ProgramType program_type = ProgramType::SysModule;
+
+    bool is_debuggable = false;
+    bool can_force_debug = false;
+};
+
+} // namespace Kernel
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index ac04d72d7930c859d6f9fa6b4d9aa7a8ccaaf294..07aa7a1cd67e0991e6dac289f724fff52466a1a0 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -129,7 +129,10 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
         return ResultStatus::Error32BitISA;
     }
 
-    process.LoadFromMetadata(metadata);
+    if (process.LoadFromMetadata(metadata).IsError()) {
+        return ResultStatus::ErrorUnableToParseKernelMetadata;
+    }
+
     const FileSys::PatchManager pm(metadata.GetTitleID());
 
     // Load NSO modules
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 9cd0b0ccd0a39d430a34dd22148d9c0df8734d84..d8cc3095955d13cfe9c3b53974fd5bfc66a94f3f 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
     return "unknown";
 }
 
-constexpr std::array<const char*, 60> RESULT_MESSAGES{
+constexpr std::array<const char*, 62> RESULT_MESSAGES{
     "The operation completed successfully.",
     "The loader requested to load is already loaded.",
     "The operation is not implemented.",
@@ -103,6 +103,7 @@ constexpr std::array<const char*, 60> RESULT_MESSAGES{
     "The NPDM has a bad ACI header,",
     "The NPDM file has a bad file access control.",
     "The NPDM has a bad file access header.",
+    "The NPDM has bad kernel capability descriptors.",
     "The PFS/HFS partition has a bad header.",
     "The PFS/HFS partition has incorrect size as determined by the header.",
     "The NCA file has a bad header.",
@@ -125,6 +126,7 @@ constexpr std::array<const char*, 60> RESULT_MESSAGES{
     "The file could not be found or does not exist.",
     "The game is missing a program metadata file (main.npdm).",
     "The game uses the currently-unimplemented 32-bit architecture.",
+    "Unable to completely parse the kernel metadata when loading the emulated process",
     "The RomFS could not be found.",
     "The ELF file has incorrect size as determined by the header.",
     "There was a general error loading the NRO into emulated memory.",
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index cfd67adc0fdf25109eaf136059848dc8a7de1078..30eacd803b6a9212dc87c5f1404a91bb124b6888 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -71,6 +71,7 @@ enum class ResultStatus : u16 {
     ErrorBadACIHeader,
     ErrorBadFileAccessControl,
     ErrorBadFileAccessHeader,
+    ErrorBadKernelCapabilityDescriptors,
     ErrorBadPFSHeader,
     ErrorIncorrectPFSFileSize,
     ErrorBadNCAHeader,
@@ -93,6 +94,7 @@ enum class ResultStatus : u16 {
     ErrorNullFile,
     ErrorMissingNPDM,
     Error32BitISA,
+    ErrorUnableToParseKernelMetadata,
     ErrorNoRomFS,
     ErrorIncorrectELFFileSize,
     ErrorLoadingNRO,