diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a56e526a6f..623a44e0c5 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -43,6 +43,8 @@ add_library(core STATIC
     file_sys/fsmitm_romfsbuild.h
     file_sys/ips_layer.cpp
     file_sys/ips_layer.h
+    file_sys/kernel_executable.cpp
+    file_sys/kernel_executable.h
     file_sys/mode.h
     file_sys/nca_metadata.cpp
     file_sys/nca_metadata.h
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
new file mode 100644
index 0000000000..0ddb9a60b7
--- /dev/null
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -0,0 +1,229 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/string_util.h"
+#include "core/file_sys/kernel_executable.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+constexpr u32 INI_MAX_KIPS = 0x50;
+
+namespace {
+bool DecompressBLZ(std::vector<u8>& data) {
+    if (data.size() < 0xC)
+        return {};
+
+    const auto data_size = data.size() - 0xC;
+
+    u32 compressed_size{};
+    u32 init_index{};
+    u32 additional_size{};
+    std::memcpy(&compressed_size, data.data() + data_size, sizeof(u32));
+    std::memcpy(&init_index, data.data() + data_size + 0x4, sizeof(u32));
+    std::memcpy(&additional_size, data.data() + data_size + 0x8, sizeof(u32));
+
+    const auto start_offset = data.size() - compressed_size;
+    data.resize(compressed_size + additional_size + start_offset);
+
+    std::size_t index = compressed_size - init_index;
+    std::size_t out_index = compressed_size + additional_size;
+
+    while (out_index > 0) {
+        --index;
+        auto control = data[index + start_offset];
+        for (size_t i = 0; i < 8; ++i) {
+            if ((control & 0x80) > 0) {
+                if (index < 2) {
+                    return false;
+                }
+                index -= 2;
+                std::size_t segment_offset =
+                    data[index + start_offset] | data[index + start_offset + 1] << 8;
+                std::size_t segment_size = ((segment_offset >> 12) & 0xF) + 3;
+                segment_offset &= 0xFFF;
+                segment_offset += 3;
+
+                if (out_index < segment_size)
+                    segment_size = out_index;
+
+                if (out_index < segment_size) {
+                    return false;
+                }
+
+                out_index -= segment_size;
+
+                for (size_t j = 0; j < segment_size; ++j) {
+                    if (out_index + j + segment_offset + start_offset >= data.size()) {
+                        return false;
+                    }
+                    data[out_index + j + start_offset] =
+                        data[out_index + j + segment_offset + start_offset];
+                }
+            } else {
+                if (out_index < 1) {
+                    return false;
+                }
+                --out_index;
+                --index;
+                data[out_index + start_offset] = data[index + start_offset];
+            }
+
+            control <<= 1;
+            if (out_index == 0)
+                return true;
+        }
+    }
+
+    return true;
+}
+} // Anonymous namespace
+
+KIP::KIP(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
+    if (file == nullptr) {
+        status = Loader::ResultStatus::ErrorNullFile;
+        return;
+    }
+
+    if (file->GetSize() < sizeof(KIPHeader) || file->ReadObject(&header) != sizeof(KIPHeader)) {
+        status = Loader::ResultStatus::ErrorBadKIPHeader;
+        return;
+    }
+
+    if (header.magic != Common::MakeMagic('K', 'I', 'P', '1')) {
+        status = Loader::ResultStatus::ErrorBadKIPHeader;
+        return;
+    }
+
+    u64 offset = sizeof(KIPHeader);
+    for (std::size_t i = 0; i < header.sections.size(); ++i) {
+        auto compressed = file->ReadBytes(header.sections[i].compressed_size, offset);
+        offset += header.sections[i].compressed_size;
+
+        if (header.sections[i].compressed_size == 0 && header.sections[i].decompressed_size != 0) {
+            decompressed_sections[i] = std::vector<u8>(header.sections[i].decompressed_size);
+        } else if (header.sections[i].compressed_size == header.sections[i].decompressed_size) {
+            decompressed_sections[i] = std::move(compressed);
+        } else {
+            decompressed_sections[i] = compressed;
+            if (!DecompressBLZ(decompressed_sections[i])) {
+                status = Loader::ResultStatus::ErrorBLZDecompressionFailed;
+                return;
+            }
+        }
+    }
+}
+
+Loader::ResultStatus KIP::GetStatus() const {
+    return status;
+}
+
+std::string KIP::GetName() const {
+    return Common::StringFromFixedZeroTerminatedBuffer(header.name.data(), header.name.size());
+}
+
+u64 KIP::GetTitleID() const {
+    return header.title_id;
+}
+
+std::vector<u8> KIP::GetSectionDecompressed(u8 index) const {
+    return decompressed_sections[index];
+}
+
+bool KIP::Is64Bit() const {
+    return header.flags & 0x8;
+}
+
+bool KIP::Is39BitAddressSpace() const {
+    return header.flags & 0x10;
+}
+
+bool KIP::IsService() const {
+    return header.flags & 0x20;
+}
+
+std::vector<u32> KIP::GetKernelCapabilities() const {
+    return std::vector(header.capabilities.begin(), header.capabilities.end());
+}
+
+s32 KIP::GetMainThreadPriority() const {
+    return header.main_thread_priority;
+}
+
+u32 KIP::GetMainThreadStackSize() const {
+    return header.sections[1].attribute;
+}
+
+u32 KIP::GetMainThreadCpuCore() const {
+    return header.default_core;
+}
+
+const std::vector<u8>& KIP::GetTextSection() const {
+    return decompressed_sections[0];
+}
+
+const std::vector<u8>& KIP::GetRODataSection() const {
+    return decompressed_sections[1];
+}
+
+const std::vector<u8>& KIP::GetDataSection() const {
+    return decompressed_sections[2];
+}
+
+u32 KIP::GetTextOffset() const {
+    return header.sections[0].offset;
+}
+
+u32 KIP::GetRODataOffset() const {
+    return header.sections[1].offset;
+}
+
+u32 KIP::GetDataOffset() const {
+    return header.sections[2].offset;
+}
+
+u32 KIP::GetBSSSize() const {
+    return header.sections[3].decompressed_size;
+}
+
+u32 KIP::GetBSSOffset() const {
+    return header.sections[3].offset;
+}
+
+INI::INI(const VirtualFile& file) : status(Loader::ResultStatus::Success) {
+    if (file->GetSize() < sizeof(INIHeader) || file->ReadObject(&header) != sizeof(INIHeader)) {
+        status = Loader::ResultStatus::ErrorBadINIHeader;
+        return;
+    }
+
+    if (header.magic != Common::MakeMagic('I', 'N', 'I', '1')) {
+        status = Loader::ResultStatus::ErrorBadINIHeader;
+        return;
+    }
+
+    if (header.kip_count > INI_MAX_KIPS) {
+        status = Loader::ResultStatus::ErrorINITooManyKIPs;
+        return;
+    }
+
+    u64 offset = sizeof(INIHeader);
+    for (std::size_t i = 0; i < header.kip_count; ++i) {
+        const auto kip_file =
+            std::make_shared<OffsetVfsFile>(file, file->GetSize() - offset, offset);
+        KIP kip(kip_file);
+        if (kip.GetStatus() == Loader::ResultStatus::Success) {
+            kips.push_back(std::move(kip));
+        }
+    }
+}
+
+Loader::ResultStatus INI::GetStatus() const {
+    return status;
+}
+
+const std::vector<KIP>& INI::GetKIPs() const {
+    return kips;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
new file mode 100644
index 0000000000..324a573846
--- /dev/null
+++ b/src/core/file_sys/kernel_executable.h
@@ -0,0 +1,99 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/swap.h"
+#include "core/file_sys/vfs_types.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+struct KIPSectionHeader {
+    u32_le offset;
+    u32_le decompressed_size;
+    u32_le compressed_size;
+    u32_le attribute;
+};
+static_assert(sizeof(KIPSectionHeader) == 0x10, "KIPSectionHeader has incorrect size.");
+
+struct KIPHeader {
+    u32_le magic;
+    std::array<char, 0xC> name;
+    u64_le title_id;
+    u32_le process_category;
+    u8 main_thread_priority;
+    u8 default_core;
+    INSERT_PADDING_BYTES(1);
+    u8 flags;
+    std::array<KIPSectionHeader, 6> sections;
+    std::array<u32, 0x20> capabilities;
+};
+static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
+
+struct INIHeader {
+    u32_le magic;
+    u32_le size;
+    u32_le kip_count;
+    INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
+
+// Kernel Internal Process
+class KIP {
+public:
+    explicit KIP(const VirtualFile& file);
+
+    Loader::ResultStatus GetStatus() const;
+
+    std::string GetName() const;
+    u64 GetTitleID() const;
+    std::vector<u8> GetSectionDecompressed(u8 index) const;
+
+    // Executable Flags
+    bool Is64Bit() const;
+    bool Is39BitAddressSpace() const;
+    bool IsService() const;
+
+    std::vector<u32> GetKernelCapabilities() const;
+
+    s32 GetMainThreadPriority() const;
+    u32 GetMainThreadStackSize() const;
+    u32 GetMainThreadCpuCore() const;
+
+    const std::vector<u8>& GetTextSection() const;
+    const std::vector<u8>& GetRODataSection() const;
+    const std::vector<u8>& GetDataSection() const;
+
+    u32 GetTextOffset() const;
+    u32 GetRODataOffset() const;
+    u32 GetDataOffset() const;
+
+    u32 GetBSSSize() const;
+    u32 GetBSSOffset() const;
+
+private:
+    Loader::ResultStatus status;
+
+    KIPHeader header{};
+    std::array<std::vector<u8>, 6> decompressed_sections;
+};
+
+class INI {
+public:
+    explicit INI(const VirtualFile& file);
+
+    Loader::ResultStatus GetStatus() const;
+
+    const std::vector<KIP>& GetKIPs() const;
+
+private:
+    Loader::ResultStatus status;
+
+    INIHeader header{};
+    std::vector<KIP> kips;
+};
+
+} // namespace FileSys