From df5b75694f5abde94ccf05fa6c7a557b1ba9079b Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Fri, 27 Jul 2018 23:55:23 -0400
Subject: [PATCH 01/13] Remove files that are not used

---
 .gitmodules                              |   3 +
 externals/CMakeLists.txt                 |   4 +
 externals/mbedtls                        |   1 +
 src/common/file_util.cpp                 |  12 +
 src/common/file_util.h                   |   2 +
 src/common/logging/backend.cpp           |   1 +
 src/common/logging/log.h                 |   1 +
 src/core/CMakeLists.txt                  |  11 +
 src/core/crypto/aes_util.cpp             |   6 +
 src/core/crypto/aes_util.h               | 118 +++++++
 src/core/crypto/ctr_encryption_layer.cpp |  56 ++++
 src/core/crypto/ctr_encryption_layer.h   |  31 ++
 src/core/crypto/encryption_layer.cpp     |  42 +++
 src/core/crypto/encryption_layer.h       |  30 ++
 src/core/crypto/key_manager.cpp          | 410 +++++++++++++++++++++++
 src/core/crypto/key_manager.h            | 116 +++++++
 src/core/crypto/sha_util.cpp             |   5 +
 src/core/crypto/sha_util.h               |  20 ++
 src/core/file_sys/card_image.cpp         | 150 +++++++++
 src/core/file_sys/card_image.h           |  93 +++++
 src/core/file_sys/content_archive.cpp    | 168 ++++++++--
 src/core/file_sys/content_archive.h      |  26 +-
 src/core/file_sys/vfs.cpp                |  21 ++
 src/core/file_sys/vfs.h                  |   3 +
 src/core/loader/loader.cpp               |   9 +
 src/core/loader/loader.h                 |   1 +
 src/core/loader/nca.cpp                  |  19 +-
 src/core/loader/nca.h                    |   2 +
 src/core/loader/xci.cpp                  |  67 ++++
 src/core/loader/xci.h                    |  42 +++
 src/core/settings.h                      |   2 +
 src/yuzu/configuration/config.cpp        |   2 +
 src/yuzu/game_list.cpp                   |   2 +-
 src/yuzu/main.cpp                        |  14 +
 src/yuzu_cmd/config.cpp                  |   1 +
 src/yuzu_cmd/yuzu.cpp                    |  15 +
 36 files changed, 1463 insertions(+), 43 deletions(-)
 create mode 160000 externals/mbedtls
 create mode 100644 src/core/crypto/aes_util.cpp
 create mode 100644 src/core/crypto/aes_util.h
 create mode 100644 src/core/crypto/ctr_encryption_layer.cpp
 create mode 100644 src/core/crypto/ctr_encryption_layer.h
 create mode 100644 src/core/crypto/encryption_layer.cpp
 create mode 100644 src/core/crypto/encryption_layer.h
 create mode 100644 src/core/crypto/key_manager.cpp
 create mode 100644 src/core/crypto/key_manager.h
 create mode 100644 src/core/crypto/sha_util.cpp
 create mode 100644 src/core/crypto/sha_util.h
 create mode 100644 src/core/file_sys/card_image.cpp
 create mode 100644 src/core/file_sys/card_image.h
 create mode 100644 src/core/loader/xci.cpp
 create mode 100644 src/core/loader/xci.h

diff --git a/.gitmodules b/.gitmodules
index 0f94613c4f..247aac535d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -25,6 +25,9 @@
 [submodule "unicorn"]
     path = externals/unicorn
     url = https://github.com/yuzu-emu/unicorn
+[submodule "externals/mbedtls"]
+	path = externals/mbedtls
+	url = https://github.com/DarkLordZach/mbedtls
 [submodule "opus"]
 	path = externals/opus
 	url = https://github.com/ogniK5377/opus.git
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 4636761e51..987d0142ad 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -35,6 +35,10 @@ set(LZ4_BUNDLED_MODE ON)
 add_subdirectory(lz4/contrib/cmake_unofficial)
 target_include_directories(lz4_static INTERFACE ./lz4/lib)
 
+# mbedtls
+add_subdirectory(mbedtls)
+target_include_directories(mbedtls PUBLIC ./mbedtls/include)
+
 # MicroProfile
 add_library(microprofile INTERFACE)
 target_include_directories(microprofile INTERFACE ./microprofile)
diff --git a/externals/mbedtls b/externals/mbedtls
new file mode 160000
index 0000000000..adc11799ac
--- /dev/null
+++ b/externals/mbedtls
@@ -0,0 +1 @@
+Subproject commit adc11799acb7a688b2715042b81eaf8644442bc4
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index b8dd92b651..89004c3c04 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -736,6 +736,18 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
     return paths[path];
 }
 
+std::string GetHactoolConfigurationPath() {
+#ifdef _WIN32
+    char path[MAX_PATH];
+    if (SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path) != S_OK)
+        return "";
+    std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH);
+    return local_path + "\\.switch";
+#else
+    return GetHomeDirectory() + "/.switch";
+#endif
+}
+
 size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
     return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
 }
diff --git a/src/common/file_util.h b/src/common/file_util.h
index bc9272d89d..d530d86c9b 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -125,6 +125,8 @@ bool SetCurrentDir(const std::string& directory);
 // directory. To be used in "multi-user" mode (that is, installed).
 const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
 
+std::string GetHactoolConfigurationPath();
+
 // Returns the path to where the sys file are
 std::string GetSysDirectory();
 
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index d86c40d267..d7d2f9a20c 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -212,6 +212,7 @@ void FileBackend::Write(const Entry& entry) {
     CLS(Input)                                                                                     \
     CLS(Network)                                                                                   \
     CLS(Loader)                                                                                    \
+    CLS(Crypto)                                                                                    \
     CLS(WebService)
 
 // GetClassName is a macro defined by Windows.h, grrr...
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 140cd8e474..5a580b004c 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -97,6 +97,7 @@ enum class Class : ClassType {
     Audio_DSP,         ///< The HLE implementation of the DSP
     Audio_Sink,        ///< Emulator audio output backend
     Loader,            ///< ROM loader
+    Crypto,            ///< Cryptographic engine/functions
     Input,             ///< Input emulation
     Network,           ///< Network emulation
     WebService,        ///< Interface to yuzu Web Services
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ccb0695e46..a69ee91465 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -12,6 +12,15 @@ add_library(core STATIC
     core_timing.h
     core_timing_util.cpp
     core_timing_util.h
+    crypto/aes_util.h
+    crypto/encryption_layer.cpp
+    crypto/encryption_layer.h
+    crypto/key_manager.cpp
+    crypto/key_manager.h
+    crypto/ctr_encryption_layer.cpp
+    crypto/ctr_encryption_layer.h
+    file_sys/card_image.cpp
+    file_sys/card_image.h
     file_sys/content_archive.cpp
     file_sys/content_archive.h
     file_sys/control_metadata.cpp
@@ -317,6 +326,8 @@ add_library(core STATIC
     loader/nro.h
     loader/nso.cpp
     loader/nso.h
+    loader/xci.cpp
+    loader/xci.h
     memory.cpp
     memory.h
     memory_hook.cpp
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
new file mode 100644
index 0000000000..46326cdec4
--- /dev/null
+++ b/src/core/crypto/aes_util.cpp
@@ -0,0 +1,6 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+namespace Crypto {
+} // namespace Crypto
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
new file mode 100644
index 0000000000..9807b92341
--- /dev/null
+++ b/src/core/crypto/aes_util.h
@@ -0,0 +1,118 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "core/file_sys/vfs.h"
+#include "mbedtls/cipher.h"
+
+namespace Crypto {
+
+enum class Mode {
+    CTR = MBEDTLS_CIPHER_AES_128_CTR,
+    ECB = MBEDTLS_CIPHER_AES_128_ECB,
+    XTS = MBEDTLS_CIPHER_AES_128_XTS,
+};
+
+enum class Op {
+    ENCRYPT,
+    DECRYPT,
+};
+
+template <typename Key, size_t KeySize = sizeof(Key)>
+struct AESCipher {
+    static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
+    static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256.");
+
+    AESCipher(Key key, Mode mode) {
+        mbedtls_cipher_init(&encryption_context);
+        mbedtls_cipher_init(&decryption_context);
+
+        ASSERT_MSG((mbedtls_cipher_setup(
+                        &encryption_context,
+                        mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
+                    mbedtls_cipher_setup(&decryption_context,
+                                         mbedtls_cipher_info_from_type(
+                                             static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
+                   "Failed to initialize mbedtls ciphers.");
+
+        ASSERT(
+            !mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
+        ASSERT(
+            !mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
+        //"Failed to set key on mbedtls ciphers.");
+    }
+
+    ~AESCipher() {
+        mbedtls_cipher_free(&encryption_context);
+        mbedtls_cipher_free(&decryption_context);
+    }
+
+    void SetIV(std::vector<u8> iv) {
+        ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) ||
+                    mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0,
+                   "Failed to set IV on mbedtls ciphers.");
+    }
+
+    template <typename Source, typename Dest>
+    void Transcode(const Source* src, size_t size, Dest* dest, Op op) {
+        size_t written = 0;
+
+        const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context;
+
+        mbedtls_cipher_reset(context);
+
+        if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
+            mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src), size,
+                                  reinterpret_cast<u8*>(dest), &written);
+            if (written != size)
+                LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
+                            size, written);
+        } else {
+            const auto block_size = mbedtls_cipher_get_block_size(context);
+
+            for (size_t offset = 0; offset < size; offset += block_size) {
+                auto length = std::min<size_t>(block_size, size - offset);
+                mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src) + offset, length,
+                                      reinterpret_cast<u8*>(dest) + offset, &written);
+                if (written != length)
+                    LOG_WARNING(Crypto,
+                                "Not all data was decrypted requested={:016X}, actual={:016X}.",
+                                length, written);
+            }
+        }
+
+        mbedtls_cipher_finish(context, nullptr, nullptr);
+    }
+
+    template <typename Source, typename Dest>
+    void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
+                      size_t sector_size, Op op) {
+        if (size % sector_size > 0) {
+            LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
+            return;
+        }
+
+        for (size_t i = 0; i < size; i += sector_size) {
+            SetIV(CalculateNintendoTweak(sector_id++));
+            Transcode<u8, u8>(reinterpret_cast<const u8*>(src) + i, sector_size,
+                              reinterpret_cast<u8*>(dest) + i, op);
+        }
+    }
+
+private:
+    mbedtls_cipher_context_t encryption_context;
+    mbedtls_cipher_context_t decryption_context;
+
+    static std::vector<u8> CalculateNintendoTweak(size_t sector_id) {
+        std::vector<u8> out(0x10);
+        for (size_t i = 0xF; i <= 0xF; --i) {
+            out[i] = sector_id & 0xFF;
+            sector_id >>= 8;
+        }
+        return out;
+    }
+};
+} // namespace Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
new file mode 100644
index 0000000000..8799496e2f
--- /dev/null
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -0,0 +1,56 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/crypto/ctr_encryption_layer.h"
+
+namespace Crypto {
+CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset)
+    : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
+      iv(16, 0) {}
+
+size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
+    if (length == 0)
+        return 0;
+
+    const auto sector_offset = offset & 0xF;
+    if (sector_offset == 0) {
+        UpdateIV(base_offset + offset);
+        std::vector<u8> raw = base->ReadBytes(length, offset);
+        if (raw.size() != length)
+            return Read(data, raw.size(), offset);
+        cipher.Transcode(raw.data(), length, data, Op::DECRYPT);
+        return length;
+    }
+
+    // offset does not fall on block boundary (0x10)
+    std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
+    UpdateIV(base_offset + offset - sector_offset);
+    cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT);
+    size_t read = 0x10 - sector_offset;
+
+    if (length + sector_offset < 0x10) {
+        memcpy_s(data, length, block.data() + sector_offset, std::min<u64>(length, read));
+        return read;
+    }
+
+    memcpy_s(data, length, block.data() + sector_offset, read);
+    return read + Read(data + read, length - read, offset + read);
+}
+
+void CTREncryptionLayer::SetIV(std::vector<u8> iv_) {
+    const auto length = std::min<size_t>(iv_.size(), iv.size());
+    for (size_t i = 0; i < length; ++i)
+        iv[i] = iv_[i];
+}
+
+void CTREncryptionLayer::UpdateIV(size_t offset) const {
+    offset >>= 4;
+    for (size_t i = 0; i < 8; ++i) {
+        iv[16 - i - 1] = offset & 0xFF;
+        offset >>= 8;
+    }
+    cipher.SetIV(iv);
+}
+} // namespace Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
new file mode 100644
index 0000000000..fe53e714b1
--- /dev/null
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "aes_util.h"
+#include "encryption_layer.h"
+#include "key_manager.h"
+
+namespace Crypto {
+
+// Sits on top of a VirtualFile and provides CTR-mode AES decription.
+struct CTREncryptionLayer : public EncryptionLayer {
+    CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset);
+
+    size_t Read(u8* data, size_t length, size_t offset) const override;
+
+    void SetIV(std::vector<u8> iv);
+
+private:
+    size_t base_offset;
+
+    // Must be mutable as operations modify cipher contexts.
+    mutable AESCipher<Key128> cipher;
+    mutable std::vector<u8> iv;
+
+    void UpdateIV(size_t offset) const;
+};
+
+} // namespace Crypto
diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp
new file mode 100644
index 0000000000..4e243051e2
--- /dev/null
+++ b/src/core/crypto/encryption_layer.cpp
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/crypto/encryption_layer.h"
+
+namespace Crypto {
+
+EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {}
+
+std::string EncryptionLayer::GetName() const {
+    return base->GetName();
+}
+
+size_t EncryptionLayer::GetSize() const {
+    return base->GetSize();
+}
+
+bool EncryptionLayer::Resize(size_t new_size) {
+    return false;
+}
+
+std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const {
+    return base->GetContainingDirectory();
+}
+
+bool EncryptionLayer::IsWritable() const {
+    return false;
+}
+
+bool EncryptionLayer::IsReadable() const {
+    return true;
+}
+
+size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) {
+    return 0;
+}
+
+bool EncryptionLayer::Rename(std::string_view name) {
+    return base->Rename(name);
+}
+} // namespace Crypto
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
new file mode 100644
index 0000000000..2312870d83
--- /dev/null
+++ b/src/core/crypto/encryption_layer.h
@@ -0,0 +1,30 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include "core/file_sys/vfs.h"
+
+namespace Crypto {
+
+// Basically non-functional class that implements all of the methods that are irrelevant to an
+// EncryptionLayer. Reduces duplicate code.
+struct EncryptionLayer : public FileSys::VfsFile {
+    explicit EncryptionLayer(FileSys::VirtualFile base);
+
+    size_t Read(u8* data, size_t length, size_t offset) const override = 0;
+
+    std::string GetName() const override;
+    size_t GetSize() const override;
+    bool Resize(size_t new_size) override;
+    std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override;
+    bool IsWritable() const override;
+    bool IsReadable() const override;
+    size_t Write(const u8* data, size_t length, size_t offset) override;
+    bool Rename(std::string_view name) override;
+
+protected:
+    FileSys::VirtualFile base;
+};
+
+} // namespace Crypto
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
new file mode 100644
index 0000000000..05c6a70a24
--- /dev/null
+++ b/src/core/crypto/key_manager.cpp
@@ -0,0 +1,410 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fstream>
+#include <locale>
+#include <sstream>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/crypto/key_manager.h"
+#include "mbedtls/sha256.h"
+
+namespace Crypto {
+KeyManager keys = {};
+
+std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_prod = {
+    {{S128KeyType::MASTER, 0, 0},
+     "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32},
+    {{S128KeyType::MASTER, 1, 0},
+     "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32},
+    {{S128KeyType::MASTER, 2, 0},
+     "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32},
+    {{S128KeyType::MASTER, 3, 0},
+     "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32},
+    {{S128KeyType::MASTER, 4, 0},
+     "75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32},
+    {{S128KeyType::PACKAGE1, 0, 0},
+     "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32},
+    {{S128KeyType::PACKAGE1, 1, 0},
+     "4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32},
+    {{S128KeyType::PACKAGE1, 2, 0},
+     "CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32},
+    {{S128KeyType::PACKAGE1, 3, 0},
+     "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32},
+    {{S128KeyType::PACKAGE1, 4, 0},
+     "28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32},
+    {{S128KeyType::PACKAGE2, 0, 0},
+     "94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32},
+    {{S128KeyType::PACKAGE2, 1, 0},
+     "7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32},
+    {{S128KeyType::PACKAGE2, 2, 0},
+     "5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32},
+    {{S128KeyType::PACKAGE2, 3, 0},
+     "BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32},
+    {{S128KeyType::PACKAGE2, 4, 0},
+     "631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32},
+    {{S128KeyType::TITLEKEK, 0, 0},
+     "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
+    {{S128KeyType::TITLEKEK, 1, 0},
+     "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
+    {{S128KeyType::TITLEKEK, 2, 0},
+     "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
+    {{S128KeyType::TITLEKEK, 3, 0},
+     "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
+    {{S128KeyType::TITLEKEK, 4, 0},
+     "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
+    {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
+     "46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
+     "592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
+     "C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
+     "7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
+     "AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
+     "5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
+     "194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
+     "CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
+     "42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
+     "1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
+     "644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32},
+};
+
+std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_prod = {
+    {{S256KeyType::HEADER, 0, 0},
+     "8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32},
+    {{S256KeyType::SD_SAVE, 0, 0},
+     "13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32},
+    {{S256KeyType::SD_NCA, 0, 0},
+     "8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32},
+};
+
+// TODO(DarkLordZach): Find missing hashes for dev keys.
+
+std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_dev = {
+    {{S128KeyType::MASTER, 0, 0},
+     "779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32},
+    {{S128KeyType::MASTER, 1, 0},
+     "0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32},
+    {{S128KeyType::MASTER, 2, 0},
+     "4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32},
+    {{S128KeyType::MASTER, 3, 0},
+     "7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32},
+    {{S128KeyType::MASTER, 4, 0},
+     "87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32},
+    {{S128KeyType::PACKAGE1, 0, 0},
+     "166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32},
+    {{S128KeyType::PACKAGE1, 1, 0},
+     "f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32},
+    {{S128KeyType::PACKAGE1, 2, 0},
+     "d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32},
+    {{S128KeyType::PACKAGE1, 3, 0},
+     "aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32},
+    {{S128KeyType::PACKAGE1, 4, 0},
+     "8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32},
+    {{S128KeyType::PACKAGE2, 0, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::PACKAGE2, 1, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::PACKAGE2, 2, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::PACKAGE2, 3, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::PACKAGE2, 4, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::TITLEKEK, 0, 0},
+     "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
+    {{S128KeyType::TITLEKEK, 1, 0},
+     "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
+    {{S128KeyType::TITLEKEK, 2, 0},
+     "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
+    {{S128KeyType::TITLEKEK, 3, 0},
+     "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
+    {{S128KeyType::TITLEKEK, 4, 0},
+     "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
+    {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+};
+
+std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_dev = {
+    {{S256KeyType::HEADER, 0, 0},
+     "ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32},
+    {{S256KeyType::SD_SAVE, 0, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+    {{S256KeyType::SD_NCA, 0, 0},
+     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
+};
+
+std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
+    {"master_key_00", {S128KeyType::MASTER, 0, 0}},
+    {"master_key_01", {S128KeyType::MASTER, 1, 0}},
+    {"master_key_02", {S128KeyType::MASTER, 2, 0}},
+    {"master_key_03", {S128KeyType::MASTER, 3, 0}},
+    {"master_key_04", {S128KeyType::MASTER, 4, 0}},
+    {"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}},
+    {"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}},
+    {"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}},
+    {"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}},
+    {"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}},
+    {"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}},
+    {"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}},
+    {"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}},
+    {"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}},
+    {"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}},
+    {"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}},
+    {"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}},
+    {"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}},
+    {"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}},
+    {"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}},
+    {"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}},
+    {"key_area_key_application_00",
+     {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_01",
+     {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_02",
+     {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_03",
+     {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_04",
+     {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_system_00",
+     {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_01",
+     {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_02",
+     {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_03",
+     {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_04",
+     {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}},
+};
+
+std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
+    {"header_key", {S256KeyType::HEADER, 0, 0}},
+    {"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}},
+    {"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}},
+};
+
+static u8 ToHexNibble(char c1) {
+    if (c1 >= 65 && c1 <= 70)
+        return c1 - 55;
+    if (c1 >= 97 && c1 <= 102)
+        return c1 - 87;
+    if (c1 >= 48 && c1 <= 57)
+        return c1 - 48;
+    throw std::logic_error("Invalid hex digit");
+}
+
+template <size_t Size>
+static std::array<u8, Size> HexStringToArray(std::string_view str) {
+    std::array<u8, Size> out{};
+    for (size_t i = 0; i < 2 * Size; i += 2) {
+        auto d1 = str[i];
+        auto d2 = str[i + 1];
+        out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
+    }
+    return out;
+}
+
+std::array<u8, 16> operator""_array16(const char* str, size_t len) {
+    if (len != 32)
+        throw std::logic_error("Not of correct size.");
+    return HexStringToArray<16>(str);
+}
+
+std::array<u8, 32> operator""_array32(const char* str, size_t len) {
+    if (len != 64)
+        throw std::logic_error("Not of correct size.");
+    return HexStringToArray<32>(str);
+}
+
+void KeyManager::SetValidationMode(bool dev) {
+    dev_mode = dev;
+}
+
+void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
+    const auto filename = std::string(filename_);
+    std::ifstream file(filename);
+    if (!file.is_open())
+        return;
+
+    std::string line;
+    while (std::getline(file, line)) {
+        std::vector<std::string> out;
+        std::stringstream stream(line);
+        std::string item;
+        while (std::getline(stream, item, '='))
+            out.push_back(std::move(item));
+
+        if (out.size() != 2)
+            continue;
+
+        out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
+        out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
+
+        if (is_title_keys) {
+            auto rights_id_raw = HexStringToArray<16>(out[0]);
+            u128 rights_id = *reinterpret_cast<std::array<u64, 2>*>(&rights_id_raw);
+            Key128 key = HexStringToArray<16>(out[1]);
+            SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]);
+        } else {
+            std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
+            if (s128_file_id.find(out[0]) != s128_file_id.end()) {
+                const auto index = s128_file_id[out[0]];
+                Key128 key = HexStringToArray<16>(out[1]);
+                SetKey(index.type, key, index.field1, index.field2);
+            } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
+                const auto index = s256_file_id[out[0]];
+                Key256 key = HexStringToArray<32>(out[1]);
+                SetKey(index.type, key, index.field1, index.field2);
+            }
+        }
+    }
+}
+
+bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) {
+    return s128_keys.find({id, field1, field2}) != s128_keys.end();
+}
+
+bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) {
+    return s256_keys.find({id, field1, field2}) != s256_keys.end();
+}
+
+Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) {
+    if (!HasKey(id, field1, field2))
+        return {};
+    return s128_keys[{id, field1, field2}];
+}
+
+Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) {
+    if (!HasKey(id, field1, field2))
+        return {};
+    return s256_keys[{id, field1, field2}];
+}
+
+void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
+    s128_keys[{id, field1, field2}] = key;
+}
+
+void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
+    s256_keys[{id, field1, field2}] = key;
+}
+
+bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) {
+    auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod;
+
+    KeyIndex<S128KeyType> id = {key, field1, field2};
+    if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY ||
+        hash.find(id) == hash.end()) {
+        LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo());
+        return true;
+    }
+
+    if (!HasKey(key, field1, field2)) {
+        LOG_CRITICAL(Crypto,
+                     "System has requested validation of [{}], but user has not added it. Add this "
+                     "key to use functionality.",
+                     id.DebugInfo());
+        return false;
+    }
+
+    SHA256Hash key_hash{};
+    const auto a_key = GetKey(key, field1, field2);
+    mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
+    if (key_hash != hash[id]) {
+        LOG_CRITICAL(Crypto,
+                     "The hash of the provided key for [{}] does not match the one on file. This "
+                     "means you probably have an incorrect key. If you believe this to be in "
+                     "error, contact the yuzu devs.",
+                     id.DebugInfo());
+        return false;
+    }
+
+    return true;
+}
+
+bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) {
+    auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod;
+
+    KeyIndex<S256KeyType> id = {key, field1, field2};
+    if (hash.find(id) == hash.end()) {
+        LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo());
+        return true;
+    }
+
+    if (!HasKey(key, field1, field2)) {
+        LOG_ERROR(Crypto,
+                  "System has requested validation of [{}], but user has not added it. Add this "
+                  "key to use functionality.",
+                  id.DebugInfo());
+        return false;
+    }
+
+    SHA256Hash key_hash{};
+    const auto a_key = GetKey(key, field1, field2);
+    mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
+    if (key_hash != hash[id]) {
+        LOG_CRITICAL(Crypto,
+                     "The hash of the provided key for [{}] does not match the one on file. This "
+                     "means you probably have an incorrect key. If you believe this to be in "
+                     "error, contact the yuzu devs.",
+                     id.DebugInfo());
+        return false;
+    }
+
+    return true;
+}
+} // namespace Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
new file mode 100644
index 0000000000..155989e460
--- /dev/null
+++ b/src/core/crypto/key_manager.h
@@ -0,0 +1,116 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include <array>
+#include <unordered_map>
+#include <vector>
+#include "common/common_types.h"
+
+namespace Crypto {
+
+typedef std::array<u8, 0x10> Key128;
+typedef std::array<u8, 0x20> Key256;
+typedef std::array<u8, 0x20> SHA256Hash;
+
+static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
+static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
+
+enum class S256KeyType : u64 {
+    HEADER,  //
+    SD_SAVE, //
+    SD_NCA,  //
+};
+
+enum class S128KeyType : u64 {
+    MASTER,          // f1=crypto revision
+    PACKAGE1,        // f1=crypto revision
+    PACKAGE2,        // f1=crypto revision
+    TITLEKEK,        // f1=crypto revision
+    ETICKET_RSA_KEK, //
+    KEY_AREA,        // f1=crypto revision f2=type {app, ocean, system}
+    SD_SEED,         //
+    TITLEKEY,        // f1=rights id LSB f2=rights id MSB
+};
+
+enum class KeyAreaKeyType : u8 {
+    Application,
+    Ocean,
+    System,
+};
+
+template <typename KeyType>
+struct KeyIndex {
+    KeyType type;
+    u64 field1;
+    u64 field2;
+
+    std::string DebugInfo() {
+        u8 key_size = 16;
+        if (std::is_same_v<KeyType, S256KeyType>)
+            key_size = 32;
+        return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size,
+                           static_cast<u8>(type), field1, field2);
+    }
+};
+
+// The following two (== and hash) are so KeyIndex can be a key in unordered_map
+
+template <typename KeyType>
+bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
+    return lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 == rhs.field2;
+}
+
+} // namespace Crypto
+
+namespace std {
+template <typename KeyType>
+struct hash<Crypto::KeyIndex<KeyType>> {
+    size_t operator()(const Crypto::KeyIndex<KeyType>& k) const {
+        using std::hash;
+
+        return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
+               (hash<u64>()(k.field2) << 1);
+    }
+};
+} // namespace std
+
+namespace Crypto {
+
+std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
+std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
+
+struct KeyManager {
+    void SetValidationMode(bool dev);
+    void LoadFromFile(std::string_view filename, bool is_title_keys);
+
+    bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
+    bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
+
+    Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
+    Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
+
+    void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
+    void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
+
+    bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0);
+    bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0);
+
+private:
+    std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
+    std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
+
+    bool dev_mode = false;
+
+    static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_prod;
+    static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_prod;
+    static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_dev;
+    static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_dev;
+    static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
+    static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
+};
+
+extern KeyManager keys;
+
+} // namespace Crypto
diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp
new file mode 100644
index 0000000000..180008a859
--- /dev/null
+++ b/src/core/crypto/sha_util.cpp
@@ -0,0 +1,5 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+namespace Crypto {} // namespace Crypto
diff --git a/src/core/crypto/sha_util.h b/src/core/crypto/sha_util.h
new file mode 100644
index 0000000000..fa3fa9d33e
--- /dev/null
+++ b/src/core/crypto/sha_util.h
@@ -0,0 +1,20 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "core/file_sys/vfs.h"
+#include "key_manager.h"
+#include "mbedtls/cipher.h"
+
+namespace Crypto {
+typedef std::array<u8, 0x20> SHA256Hash;
+
+inline SHA256Hash operator"" _HASH(const char* data, size_t len) {
+    if (len != 0x40)
+        return {};
+}
+
+} // namespace Crypto
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
new file mode 100644
index 0000000000..5ff09a362f
--- /dev/null
+++ b/src/core/file_sys/card_image.cpp
@@ -0,0 +1,150 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <string>
+#include <core/loader/loader.h>
+#include "core/file_sys/card_image.h"
+#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/vfs_offset.h"
+
+namespace FileSys {
+
+XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
+    if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
+        status = Loader::ResultStatus::ErrorInvalidFormat;
+        return;
+    }
+
+    if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
+        status = Loader::ResultStatus::ErrorInvalidFormat;
+        return;
+    }
+
+    PartitionFilesystem main_hfs(
+        std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset));
+
+    if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
+        status = main_hfs.GetStatus();
+        return;
+    }
+
+    const static std::array<std::string, 0x4> partition_names = {"update", "normal", "secure",
+                                                                 "logo"};
+
+    for (XCIPartition partition :
+         {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
+        auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]);
+        if (raw != nullptr)
+            partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
+    }
+
+    auto result = AddNCAFromPartition(XCIPartition::Secure);
+    if (result != Loader::ResultStatus::Success) {
+        status = result;
+        return;
+    }
+    result = AddNCAFromPartition(XCIPartition::Update);
+    if (result != Loader::ResultStatus::Success) {
+        status = result;
+        return;
+    }
+
+    result = AddNCAFromPartition(XCIPartition::Normal);
+    if (result != Loader::ResultStatus::Success) {
+        status = result;
+        return;
+    }
+
+    if (GetFormatVersion() >= 0x2) {
+        result = AddNCAFromPartition(XCIPartition::Logo);
+        if (result != Loader::ResultStatus::Success) {
+            status = result;
+            return;
+        }
+    }
+
+    status = Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus XCI::GetStatus() const {
+    return status;
+}
+
+VirtualDir XCI::GetPartition(XCIPartition partition) const {
+    return partitions[static_cast<size_t>(partition)];
+}
+
+VirtualDir XCI::GetSecurePartition() const {
+    return GetPartition(XCIPartition::Secure);
+}
+
+VirtualDir XCI::GetNormalPartition() const {
+    return GetPartition(XCIPartition::Normal);
+}
+
+VirtualDir XCI::GetUpdatePartition() const {
+    return GetPartition(XCIPartition::Update);
+}
+
+VirtualDir XCI::GetLogoPartition() const {
+    return GetPartition(XCIPartition::Logo);
+}
+
+std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
+    for (const auto& nca : ncas) {
+        if (nca->GetType() == type)
+            return nca;
+    }
+
+    return nullptr;
+}
+
+VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
+    auto nca = GetNCAByType(type);
+    if (nca != nullptr)
+        return nca->GetBaseFile();
+    return nullptr;
+}
+
+std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const {
+    return {};
+}
+
+std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const {
+    return std::vector<std::shared_ptr<VfsDirectory>>();
+}
+
+std::string XCI::GetName() const {
+    return file->GetName();
+}
+
+std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const {
+    return file->GetContainingDirectory();
+}
+
+bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
+    return false;
+}
+
+Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
+    if (partitions[static_cast<size_t>(part)] == nullptr) {
+        return Loader::ResultStatus::ErrorInvalidFormat;
+    }
+
+    for (VirtualFile file : partitions[static_cast<size_t>(part)]->GetFiles()) {
+        if (file->GetExtension() != "nca")
+            continue;
+        auto nca = std::make_shared<NCA>(file);
+        if (nca->GetStatus() == Loader::ResultStatus::Success)
+            ncas.push_back(std::move(nca));
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+u8 XCI::GetFormatVersion() const {
+    return GetLogoPartition() == nullptr ? 0x1 : 0x2;
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
new file mode 100644
index 0000000000..b765c8bc10
--- /dev/null
+++ b/src/core/file_sys/card_image.h
@@ -0,0 +1,93 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/swap.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+enum class GamecardSize : u8 {
+    S_1GB = 0xFA,
+    S_2GB = 0xF8,
+    S_4GB = 0xF0,
+    S_8GB = 0xE0,
+    S_16GB = 0xE1,
+    S_32GB = 0xE2,
+};
+
+struct GamecardInfo {
+    std::array<u8, 0x70> data;
+};
+static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
+
+struct GamecardHeader {
+    std::array<u8, 0x100> signature;
+    u32_le magic;
+    u32_le secure_area_start;
+    u32_le backup_area_start;
+    u8 kek_index;
+    GamecardSize size;
+    u8 header_version;
+    u8 flags;
+    u64_le package_id;
+    u64_le valid_data_end;
+    u128 info_iv;
+    u64_le hfs_offset;
+    u64_le hfs_size;
+    std::array<u8, 0x20> hfs_header_hash;
+    std::array<u8, 0x20> initial_data_hash;
+    u32_le secure_mode_flag;
+    u32_le title_key_flag;
+    u32_le key_flag;
+    u32_le normal_area_end;
+    GamecardInfo info;
+};
+static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size.");
+
+enum class XCIPartition : u8 { Update, Normal, Secure, Logo };
+
+class XCI : public ReadOnlyVfsDirectory {
+public:
+    explicit XCI(VirtualFile file);
+
+    Loader::ResultStatus GetStatus() const;
+
+    u8 GetFormatVersion() const;
+
+    VirtualDir GetPartition(XCIPartition partition) const;
+    VirtualDir GetSecurePartition() const;
+    VirtualDir GetNormalPartition() const;
+    VirtualDir GetUpdatePartition() const;
+    VirtualDir GetLogoPartition() const;
+
+    std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
+    VirtualFile GetNCAFileByType(NCAContentType type) const;
+
+    std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
+
+    std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
+
+    std::string GetName() const override;
+
+    std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
+
+protected:
+    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
+
+private:
+    Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
+
+    VirtualFile file;
+    GamecardHeader header{};
+
+    Loader::ResultStatus status;
+
+    std::vector<VirtualDir> partitions;
+    std::vector<std::shared_ptr<NCA>> ncas;
+};
+} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 61cb0bbe30..add01974e2 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -6,9 +6,12 @@
 #include <utility>
 
 #include "common/logging/log.h"
+#include "core/crypto/aes_util.h"
+#include "core/crypto/ctr_encryption_layer.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/vfs_offset.h"
 #include "core/loader/loader.h"
+#include "mbedtls/cipher.h"
 #include "romfs.h"
 
 namespace FileSys {
@@ -29,11 +32,19 @@ enum class NCASectionFilesystemType : u8 {
 struct NCASectionHeaderBlock {
     INSERT_PADDING_BYTES(3);
     NCASectionFilesystemType filesystem_type;
-    u8 crypto_type;
+    NCASectionCryptoType crypto_type;
     INSERT_PADDING_BYTES(3);
 };
 static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
 
+struct NCASectionRaw {
+    NCASectionHeaderBlock header;
+    std::array<u8, 0x138> block_data;
+    std::array<u8, 0x8> section_ctr;
+    INSERT_PADDING_BYTES(0xB8);
+};
+static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size.");
+
 struct PFS0Superblock {
     NCASectionHeaderBlock header_block;
     std::array<u8, 0x20> hash;
@@ -43,62 +54,155 @@ struct PFS0Superblock {
     u64_le hash_table_size;
     u64_le pfs0_header_offset;
     u64_le pfs0_size;
-    INSERT_PADDING_BYTES(432);
+    INSERT_PADDING_BYTES(0x1B0);
 };
 static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
 
 struct RomFSSuperblock {
     NCASectionHeaderBlock header_block;
     IVFCHeader ivfc;
+    INSERT_PADDING_BYTES(0x118);
 };
-static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
+static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
+
+union NCASectionHeader {
+    NCASectionRaw raw;
+    PFS0Superblock pfs0;
+    RomFSSuperblock romfs;
+};
+static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
+
+bool IsValidNCA(const NCAHeader& header) {
+    // TODO(DarkLordZach): Add NCA2/NCA0 support.
+    return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
+}
+
+Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
+    u8 master_key_id = header.crypto_type;
+    if (header.crypto_type_2 > master_key_id)
+        master_key_id = header.crypto_type_2;
+    if (master_key_id > 0)
+        --master_key_id;
+
+    std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
+    if (!Crypto::keys.ValidateKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index)) {
+        status = Loader::ResultStatus::ErrorEncrypted;
+        return {};
+    }
+    Crypto::AESCipher<Crypto::Key128> cipher(
+        Crypto::keys.GetKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index),
+        Crypto::Mode::ECB);
+    cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Crypto::Op::DECRYPT);
+
+    Crypto::Key128 out;
+    if (type == NCASectionCryptoType::XTS)
+        std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
+    else if (type == NCASectionCryptoType::CTR)
+        std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
+    else
+        LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
+                     static_cast<u8>(type));
+
+    u128 out_128 = *reinterpret_cast<u128*>(&out);
+    LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
+              master_key_id, header.key_index, out_128[1], out_128[0]);
+
+    return out;
+}
+
+VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) {
+    if (!encrypted)
+        return in;
+
+    switch (header.raw.header.crypto_type) {
+    case NCASectionCryptoType::NONE:
+        LOG_DEBUG(Crypto, "called with mode=NONE");
+        return in;
+    case NCASectionCryptoType::CTR:
+        LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
+        {
+            auto out = std::make_shared<Crypto::CTREncryptionLayer>(
+                std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset);
+            std::vector<u8> iv(16, 0);
+            for (u8 i = 0; i < 8; ++i)
+                iv[i] = header.raw.section_ctr[0x8 - i - 1];
+            out->SetIV(iv);
+            return out;
+        }
+    case NCASectionCryptoType::XTS:
+        // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption.
+    default:
+        LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
+                  static_cast<u8>(header.raw.header.crypto_type));
+        return nullptr;
+    }
+}
 
 NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
     if (sizeof(NCAHeader) != file->ReadObject(&header))
-        LOG_CRITICAL(Loader, "File reader errored out during header read.");
+        LOG_ERROR(Loader, "File reader errored out during header read.");
+
+    encrypted = false;
 
     if (!IsValidNCA(header)) {
-        status = Loader::ResultStatus::ErrorInvalidFormat;
-        return;
+        NCAHeader dec_header{};
+        if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
+            status = Loader::ResultStatus::ErrorEncrypted;
+            return;
+        }
+        Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
+                                                 Crypto::Mode::XTS);
+        cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, Crypto::Op::DECRYPT);
+        if (IsValidNCA(dec_header)) {
+            header = dec_header;
+            encrypted = true;
+        } else {
+            status = Loader::ResultStatus::ErrorInvalidFormat;
+            return;
+        }
     }
 
-    std::ptrdiff_t number_sections =
+    const std::ptrdiff_t number_sections =
         std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
                       [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
 
+    std::vector<NCASectionHeader> sections(number_sections);
+    const auto length_sections = SECTION_HEADER_SIZE * number_sections;
+
+    if (encrypted) {
+        auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
+        if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
+            status = Loader::ResultStatus::ErrorEncrypted;
+            return;
+        }
+        Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
+                                                 Crypto::Mode::XTS);
+        cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
+                            Crypto::Op::DECRYPT);
+    } else {
+        file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
+    }
+
     for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
-        // Seek to beginning of this section.
-        NCASectionHeaderBlock block{};
-        if (sizeof(NCASectionHeaderBlock) !=
-            file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
-            LOG_CRITICAL(Loader, "File reader errored out during header read.");
-
-        if (block.filesystem_type == NCASectionFilesystemType::ROMFS) {
-            RomFSSuperblock sb{};
-            if (sizeof(RomFSSuperblock) !=
-                file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
-                LOG_CRITICAL(Loader, "File reader errored out during header read.");
+        auto section = sections[i];
 
+        if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
             const size_t romfs_offset =
                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
-                sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
-            const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
-            files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
+                section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+            const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
+            files.emplace_back(
+                Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
+                        romfs_offset));
             romfs = files.back();
-        } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {
-            PFS0Superblock sb{};
-            // Seek back to beginning of this section.
-            if (sizeof(PFS0Superblock) !=
-                file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
-                LOG_CRITICAL(Loader, "File reader errored out during header read.");
-
+        } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
                           MEDIA_OFFSET_MULTIPLIER) +
-                         sb.pfs0_header_offset;
+                         section.pfs0.pfs0_header_offset;
             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
                                                   header.section_tables[i].media_offset);
             auto npfs = std::make_shared<PartitionFilesystem>(
-                std::make_shared<OffsetVfsFile>(file, size, offset));
+                Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset));
 
             if (npfs->GetStatus() == Loader::ResultStatus::Success) {
                 dirs.emplace_back(npfs);
@@ -153,6 +257,10 @@ VirtualDir NCA::GetExeFS() const {
     return exefs;
 }
 
+VirtualFile NCA::GetBaseFile() const {
+    return file;
+}
+
 bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
     return false;
 }
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 0b8b9db612..1e8d9c8aed 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -12,10 +12,10 @@
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
+#include "core/crypto/key_manager.h"
 #include "core/file_sys/partition_filesystem.h"
 
 namespace FileSys {
-
 enum class NCAContentType : u8 {
     Program = 0,
     Meta = 1,
@@ -24,6 +24,13 @@ enum class NCAContentType : u8 {
     Data = 4,
 };
 
+enum class NCASectionCryptoType : u8 {
+    NONE = 1,
+    XTS = 2,
+    CTR = 3,
+    BKTR = 4,
+};
+
 struct NCASectionTableEntry {
     u32_le media_offset;
     u32_le media_end_offset;
@@ -48,20 +55,19 @@ struct NCAHeader {
     std::array<u8, 0x10> rights_id;
     std::array<NCASectionTableEntry, 0x4> section_tables;
     std::array<std::array<u8, 0x20>, 0x4> hash_tables;
-    std::array<std::array<u8, 0x10>, 0x4> key_area;
+    std::array<u8, 0x40> key_area;
     INSERT_PADDING_BYTES(0xC0);
 };
 static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
 
+union NCASectionHeader;
+
 inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
     // According to switchbrew, an exefs must only contain these two files:
     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
 }
 
-inline bool IsValidNCA(const NCAHeader& header) {
-    return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
-           header.magic == Common::MakeMagic('N', 'C', 'A', '3');
-}
+bool IsValidNCA(const NCAHeader& header);
 
 // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
 // After construction, use GetStatus to determine if the file is valid and ready to be used.
@@ -81,6 +87,8 @@ public:
     VirtualFile GetRomFS() const;
     VirtualDir GetExeFS() const;
 
+    VirtualFile GetBaseFile() const;
+
 protected:
     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 
@@ -95,6 +103,12 @@ private:
     NCAHeader header{};
 
     Loader::ResultStatus status{};
+
+    bool encrypted;
+
+    Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
+
+    VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, size_t starting_offset);
 };
 
 } // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index 84a6a73978..57d0aeb854 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -285,6 +285,27 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
     return false;
 }
 
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) {
+    if (file1->GetSize() != file2->GetSize())
+        return false;
+
+    std::vector<u8> f1_v(block_size);
+    std::vector<u8> f2_v(block_size);
+    for (size_t i = 0; i < file1->GetSize(); i += block_size) {
+        auto f1_vs = file1->Read(f1_v.data(), block_size, i);
+        auto f2_vs = file2->Read(f2_v.data(), block_size, i);
+
+        if (f1_vs != f2_vs)
+            return false;
+        for (size_t j = 0; j < f1_vs; ++j) {
+            if (f1_v[j] != f2_v[j])
+                return false;
+        }
+    }
+
+    return true;
+}
+
 bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
     if (src == nullptr || dest == nullptr)
         return false;
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index cf871edd67..fab9e2b45f 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -245,6 +245,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory {
     bool Rename(std::string_view name) override;
 };
 
+// Compare the two files, byte-for-byte, in increments specificed by block_size
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200);
+
 // A method that copies the raw data between two different implementations of VirtualFile. If you
 // are using the same implementation, it is probably better to use the Copy method in the parent
 // directory of src/dest.
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index cbc4177c61..57e6c03653 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -13,6 +13,7 @@
 #include "core/loader/nca.h"
 #include "core/loader/nro.h"
 #include "core/loader/nso.h"
+#include "core/loader/xci.h"
 
 namespace Loader {
 
@@ -35,6 +36,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
     CHECK_TYPE(NSO)
     CHECK_TYPE(NRO)
     CHECK_TYPE(NCA)
+    CHECK_TYPE(XCI)
 
 #undef CHECK_TYPE
 
@@ -60,6 +62,8 @@ FileType GuessFromFilename(const std::string& name) {
         return FileType::NSO;
     if (extension == "nca")
         return FileType::NCA;
+    if (extension == "xci")
+        return FileType::XCI;
 
     return FileType::Unknown;
 }
@@ -74,6 +78,8 @@ const char* GetFileTypeString(FileType type) {
         return "NSO";
     case FileType::NCA:
         return "NCA";
+    case FileType::XCI:
+        return "XCI";
     case FileType::DeconstructedRomDirectory:
         return "Directory";
     case FileType::Error:
@@ -111,6 +117,9 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
     case FileType::NCA:
         return std::make_unique<AppLoader_NCA>(std::move(file));
 
+    case FileType::XCI:
+        return std::make_unique<AppLoader_XCI>(std::move(file));
+
     // NX deconstructed ROM directory.
     case FileType::DeconstructedRomDirectory:
         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index fbf11e5d0f..dc8d283114 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -31,6 +31,7 @@ enum class FileType {
     NSO,
     NRO,
     NCA,
+    XCI,
     DeconstructedRomDirectory,
 };
 
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index c80df23be0..a1f8235d12 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -25,12 +25,10 @@ namespace Loader {
 AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
 
 FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
-    // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
-    FileSys::NCAHeader header{};
-    if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header))
-        return FileType::Error;
+    FileSys::NCA nca(file);
 
-    if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program)
+    if (nca.GetStatus() == ResultStatus::Success &&
+        nca.GetType() == FileSys::NCAContentType::Program)
         return FileType::NCA;
 
     return FileType::Error;
@@ -98,12 +96,21 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
 }
 
 ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
-    if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0)
+    if (nca == nullptr)
+        return ResultStatus::ErrorNotLoaded;
+    if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0)
         return ResultStatus::ErrorNotUsed;
     dir = nca->GetRomFS();
     return ResultStatus::Success;
 }
 
+ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
+    if (nca == nullptr)
+        return ResultStatus::ErrorNotLoaded;
+    out_program_id = nca->GetTitleId();
+    return ResultStatus::Success;
+}
+
 AppLoader_NCA::~AppLoader_NCA() = default;
 
 } // namespace Loader
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 52c95953ac..fb96c0a22e 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -33,6 +33,8 @@ public:
 
     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
 
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+
     ~AppLoader_NCA();
 
 private:
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
new file mode 100644
index 0000000000..9759e33d1e
--- /dev/null
+++ b/src/core/loader/xci.cpp
@@ -0,0 +1,67 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/program_metadata.h"
+#include "core/file_sys/romfs.h"
+#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/service/filesystem/filesystem.h"
+#include "core/loader/nso.h"
+#include "core/loader/xci.h"
+#include "core/memory.h"
+
+namespace Loader {
+
+AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
+    : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
+      nca_loader(std::make_unique<AppLoader_NCA>(
+          xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {}
+
+FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
+    FileSys::XCI xci(file);
+
+    if (xci.GetStatus() == ResultStatus::Success &&
+        xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
+        AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
+            FileType::NCA)
+        return FileType::XCI;
+
+    return FileType::Error;
+}
+
+ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
+    if (is_loaded) {
+        return ResultStatus::ErrorAlreadyLoaded;
+    }
+
+    auto result = nca_loader->Load(process);
+    if (result != ResultStatus::Success)
+        return result;
+
+    is_loaded = true;
+
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) {
+    return nca_loader->ReadRomFS(dir);
+}
+
+ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
+    return nca_loader->ReadProgramId(out_program_id);
+}
+
+AppLoader_XCI::~AppLoader_XCI() = default;
+
+} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
new file mode 100644
index 0000000000..a9cee1ca35
--- /dev/null
+++ b/src/core/loader/xci.h
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/card_image.h"
+#include "core/loader/nca.h"
+
+namespace Loader {
+
+/// Loads an XCI file
+class AppLoader_XCI final : public AppLoader {
+public:
+    explicit AppLoader_XCI(FileSys::VirtualFile file);
+
+    /**
+     * Returns the type of the file
+     * @param file std::shared_ptr<VfsFile> open file
+     * @return FileType found, or FileType::Error if this loader doesn't know it
+     */
+    static FileType IdentifyType(const FileSys::VirtualFile& file);
+
+    FileType GetFileType() override {
+        return IdentifyType(file);
+    }
+
+    ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
+
+    ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+
+    ~AppLoader_XCI();
+
+private:
+    FileSys::ProgramMetadata metadata;
+
+    std::unique_ptr<FileSys::XCI> xci;
+    std::unique_ptr<AppLoader_NCA> nca_loader;
+};
+
+} // namespace Loader
diff --git a/src/core/settings.h b/src/core/settings.h
index 8cc65e4347..55da39b6cf 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -137,6 +137,8 @@ struct Values {
 
     std::string log_filter;
 
+    bool use_dev_keys;
+
     // Audio
     std::string sink_id;
     std::string audio_device_id;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index e8b3a9866a..2f2d8c4c5e 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -109,6 +109,7 @@ void Config::ReadValues() {
 
     qt_config->beginGroup("Miscellaneous");
     Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
+    Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool();
     qt_config->endGroup();
 
     qt_config->beginGroup("Debugging");
@@ -218,6 +219,7 @@ void Config::SaveValues() {
 
     qt_config->beginGroup("Miscellaneous");
     qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
+    qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys);
     qt_config->endGroup();
 
     qt_config->beginGroup("Debugging");
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 99e6634a15..71e24a9e22 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -365,7 +365,7 @@ void GameList::LoadInterfaceLayout() {
     item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
 }
 
-const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"};
+const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"};
 
 static bool HasSupportedFileExtension(const std::string& file_name) {
     QFileInfo file = QFileInfo(file_name.c_str());
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index be38cfa9bc..d3523e6ac2 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -13,6 +13,7 @@
 #include <QMessageBox>
 #include <QtGui>
 #include <QtWidgets>
+#include <core/crypto/key_manager.h>
 #include "common/common_paths.h"
 #include "common/logging/backend.h"
 #include "common/logging/filter.h"
@@ -88,6 +89,19 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     ui.setupUi(this);
     statusBar()->hide();
 
+    // Initialize keys
+    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
+    if (Settings::values.use_dev_keys) {
+        Crypto::keys.SetValidationMode(true);
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
+            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
+    } else {
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
+            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
+    }
+    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
+        Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
+
     default_theme_paths = QIcon::themeSearchPaths();
     UpdateUITheme();
 
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c581e96997..9bf26717fe 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -119,6 +119,7 @@ void Config::ReadValues() {
 
     // Miscellaneous
     Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
+    Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false);
 
     // Debugging
     Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index b5392c499b..955e2ba140 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -23,6 +23,7 @@
 #include "yuzu_cmd/emu_window/emu_window_sdl2.h"
 
 #include <getopt.h>
+#include "core/crypto/key_manager.h"
 #ifndef _MSC_VER
 #include <unistd.h>
 #endif
@@ -71,6 +72,20 @@ static void InitializeLogging() {
 /// Application entry point
 int main(int argc, char** argv) {
     Config config;
+
+    // Initialize keys
+    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
+    if (Settings::values.use_dev_keys) {
+        Crypto::keys.SetValidationMode(true);
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
+            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
+    } else {
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
+            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
+    }
+    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
+        Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
+
     int option_index = 0;
     bool use_gdbstub = Settings::values.use_gdbstub;
     u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);

From c54a10cb4f9912aa0827b8a1b007757252fc90ae Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 28 Jul 2018 14:28:14 -0400
Subject: [PATCH 02/13] Update mbedtls and fix compile error

---
 externals/mbedtls             | 2 +-
 src/core/crypto/key_manager.h | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/externals/mbedtls b/externals/mbedtls
index adc11799ac..06442b840e 160000
--- a/externals/mbedtls
+++ b/externals/mbedtls
@@ -1 +1 @@
-Subproject commit adc11799acb7a688b2715042b81eaf8644442bc4
+Subproject commit 06442b840ea62e2ceda0dc58f255cfff49fed5b6
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 155989e460..b892a83f2c 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -6,6 +6,7 @@
 #include <array>
 #include <unordered_map>
 #include <vector>
+#include <fmt/format.h>
 #include "common/common_types.h"
 
 namespace Crypto {

From 83c3ae8be87463486239246a93f9dadf5d2b27bd Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 28 Jul 2018 14:42:16 -0400
Subject: [PATCH 03/13] Add missing string.h include

---
 src/core/crypto/ctr_encryption_layer.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 8799496e2f..6a0d396ed1 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <string.h>
 #include "common/assert.h"
 #include "core/crypto/ctr_encryption_layer.h"
 

From 22342487e8fb851a9837db22408db56240aa6931 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 28 Jul 2018 16:23:00 -0400
Subject: [PATCH 04/13] Extract mbedtls to cpp file

---
 src/common/file_util.cpp            |   2 +-
 src/core/CMakeLists.txt             |   1 +
 src/core/crypto/aes_util.cpp        | 102 ++++++++++++++++++++++++++-
 src/core/crypto/aes_util.h          | 104 ++++++----------------------
 src/core/file_sys/content_archive.h |   3 +-
 5 files changed, 126 insertions(+), 86 deletions(-)

diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 89004c3c04..a26702f54c 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -739,7 +739,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
 std::string GetHactoolConfigurationPath() {
 #ifdef _WIN32
     char path[MAX_PATH];
-    if (SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path) != S_OK)
+    if (SHGetFolderPathA(nullptr, CSIDL_PROFILE, nullptr, 0, path) != S_OK)
         return "";
     std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH);
     return local_path + "\\.switch";
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a69ee91465..528d96a58b 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -12,6 +12,7 @@ add_library(core STATIC
     core_timing.h
     core_timing_util.cpp
     core_timing_util.h
+    crypto/aes_util.cpp
     crypto/aes_util.h
     crypto/encryption_layer.cpp
     crypto/encryption_layer.h
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 46326cdec4..a9646e52f3 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -2,5 +2,103 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-namespace Crypto {
-} // namespace Crypto
+#include "core/crypto/aes_util.h"
+#include "mbedtls/cipher.h"
+
+namespace Core::Crypto {
+static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR), "CTR mode is incorrect.");
+static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB), "ECB mode is incorrect.");
+static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS), "XTS mode is incorrect.");
+
+template<typename Key, size_t KeySize>
+Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) {
+    mbedtls_cipher_init(encryption_context.get());
+    mbedtls_cipher_init(decryption_context.get());
+
+    ASSERT_MSG((mbedtls_cipher_setup(
+            encryption_context.get(),
+            mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
+                mbedtls_cipher_setup(decryption_context.get(),
+                                     mbedtls_cipher_info_from_type(
+                                             static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
+               "Failed to initialize mbedtls ciphers.");
+
+    ASSERT(
+            !mbedtls_cipher_setkey(encryption_context.get(), key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
+    ASSERT(
+            !mbedtls_cipher_setkey(decryption_context.get(), key.data(), KeySize * 8, MBEDTLS_DECRYPT));
+    //"Failed to set key on mbedtls ciphers.");
+}
+
+template<typename Key, size_t KeySize>
+AESCipher<Key, KeySize>::~AESCipher() {
+    mbedtls_cipher_free(encryption_context.get());
+    mbedtls_cipher_free(decryption_context.get());
+}
+
+template<typename Key, size_t KeySize>
+void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
+    ASSERT_MSG((mbedtls_cipher_set_iv(encryption_context.get(), iv.data(), iv.size()) ||
+                mbedtls_cipher_set_iv(decryption_context.get(), iv.data(), iv.size())) == 0,
+               "Failed to set IV on mbedtls ciphers.");
+}
+
+template<typename Key, size_t KeySize>
+void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op)  {
+    size_t written = 0;
+
+    const auto context = op == Op::Encrypt ? encryption_context.get() : decryption_context.get();
+
+    mbedtls_cipher_reset(context);
+
+    if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
+        mbedtls_cipher_update(context, src, size,
+                              dest, &written);
+        if (written != size)
+            LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
+                        size, written);
+    } else {
+        const auto block_size = mbedtls_cipher_get_block_size(context);
+
+        for (size_t offset = 0; offset < size; offset += block_size) {
+            auto length = std::min<size_t>(block_size, size - offset);
+            mbedtls_cipher_update(context, src + offset, length,
+                                  dest + offset, &written);
+            if (written != length)
+                LOG_WARNING(Crypto,
+                            "Not all data was decrypted requested={:016X}, actual={:016X}.",
+                            length, written);
+        }
+    }
+
+    mbedtls_cipher_finish(context, nullptr, nullptr);
+}
+
+template<typename Key, size_t KeySize>
+void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size,
+                                           Op op) {
+    if (size % sector_size > 0) {
+        LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
+        return;
+    }
+
+    for (size_t i = 0; i < size; i += sector_size) {
+        SetIV(CalculateNintendoTweak(sector_id++));
+        Transcode<u8, u8>(src + i, sector_size,
+                          dest + i, op);
+    }
+}
+
+template<typename Key, size_t KeySize>
+std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id) {
+    std::vector<u8> out(0x10);
+    for (size_t i = 0xF; i <= 0xF; --i) {
+        out[i] = sector_id & 0xFF;
+        sector_id >>= 8;
+    }
+    return out;
+}
+
+template class AESCipher<Key128>;
+template class AESCipher<Key256>;
+}
\ No newline at end of file
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index 9807b92341..5c09718b22 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -6,113 +6,53 @@
 
 #include "common/assert.h"
 #include "core/file_sys/vfs.h"
-#include "mbedtls/cipher.h"
 
-namespace Crypto {
+namespace Core::Crypto {
 
 enum class Mode {
-    CTR = MBEDTLS_CIPHER_AES_128_CTR,
-    ECB = MBEDTLS_CIPHER_AES_128_ECB,
-    XTS = MBEDTLS_CIPHER_AES_128_XTS,
+    CTR = 11,
+    ECB = 2,
+    XTS = 70,
 };
 
 enum class Op {
-    ENCRYPT,
-    DECRYPT,
+    Encrypt,
+    Decrypt,
 };
 
+struct mbedtls_cipher_context_t;
+
 template <typename Key, size_t KeySize = sizeof(Key)>
-struct AESCipher {
+class AESCipher {
     static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
     static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256.");
 
-    AESCipher(Key key, Mode mode) {
-        mbedtls_cipher_init(&encryption_context);
-        mbedtls_cipher_init(&decryption_context);
+public:
+    AESCipher(Key key, Mode mode);
 
-        ASSERT_MSG((mbedtls_cipher_setup(
-                        &encryption_context,
-                        mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
-                    mbedtls_cipher_setup(&decryption_context,
-                                         mbedtls_cipher_info_from_type(
-                                             static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
-                   "Failed to initialize mbedtls ciphers.");
+    ~AESCipher();
 
-        ASSERT(
-            !mbedtls_cipher_setkey(&encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
-        ASSERT(
-            !mbedtls_cipher_setkey(&decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
-        //"Failed to set key on mbedtls ciphers.");
-    }
-
-    ~AESCipher() {
-        mbedtls_cipher_free(&encryption_context);
-        mbedtls_cipher_free(&decryption_context);
-    }
-
-    void SetIV(std::vector<u8> iv) {
-        ASSERT_MSG((mbedtls_cipher_set_iv(&encryption_context, iv.data(), iv.size()) ||
-                    mbedtls_cipher_set_iv(&decryption_context, iv.data(), iv.size())) == 0,
-                   "Failed to set IV on mbedtls ciphers.");
-    }
+    void SetIV(std::vector<u8> iv);
 
     template <typename Source, typename Dest>
     void Transcode(const Source* src, size_t size, Dest* dest, Op op) {
-        size_t written = 0;
-
-        const auto context = op == Op::ENCRYPT ? &encryption_context : &decryption_context;
-
-        mbedtls_cipher_reset(context);
-
-        if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
-            mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src), size,
-                                  reinterpret_cast<u8*>(dest), &written);
-            if (written != size)
-                LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
-                            size, written);
-        } else {
-            const auto block_size = mbedtls_cipher_get_block_size(context);
-
-            for (size_t offset = 0; offset < size; offset += block_size) {
-                auto length = std::min<size_t>(block_size, size - offset);
-                mbedtls_cipher_update(context, reinterpret_cast<const u8*>(src) + offset, length,
-                                      reinterpret_cast<u8*>(dest) + offset, &written);
-                if (written != length)
-                    LOG_WARNING(Crypto,
-                                "Not all data was decrypted requested={:016X}, actual={:016X}.",
-                                length, written);
-            }
-        }
-
-        mbedtls_cipher_finish(context, nullptr, nullptr);
+        Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op);
     }
 
+    void Transcode(const u8* src, size_t size, u8* dest, Op op);
+
     template <typename Source, typename Dest>
     void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
                       size_t sector_size, Op op) {
-        if (size % sector_size > 0) {
-            LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
-            return;
-        }
-
-        for (size_t i = 0; i < size; i += sector_size) {
-            SetIV(CalculateNintendoTweak(sector_id++));
-            Transcode<u8, u8>(reinterpret_cast<const u8*>(src) + i, sector_size,
-                              reinterpret_cast<u8*>(dest) + i, op);
-        }
+        XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id, sector_size, op);
     }
 
+    void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, Op op);
+
 private:
-    mbedtls_cipher_context_t encryption_context;
-    mbedtls_cipher_context_t decryption_context;
+    std::unique_ptr<mbedtls_cipher_context_t> encryption_context;
+    std::unique_ptr<mbedtls_cipher_context_t> decryption_context;
 
-    static std::vector<u8> CalculateNintendoTweak(size_t sector_id) {
-        std::vector<u8> out(0x10);
-        for (size_t i = 0xF; i <= 0xF; --i) {
-            out[i] = sector_id & 0xFF;
-            sector_id >>= 8;
-        }
-        return out;
-    }
+    static std::vector<u8> CalculateNintendoTweak(size_t sector_id);
 };
 } // namespace Crypto
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 1e8d9c8aed..d9ad3bf7ed 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "core/loader/loader.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -108,7 +109,7 @@ private:
 
     Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
 
-    VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, size_t starting_offset);
+    VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
 };
 
 } // namespace FileSys

From 239a3113e4c6a53a2c7b12e67a0f21afae24b0aa Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 28 Jul 2018 21:39:42 -0400
Subject: [PATCH 05/13] Make XCI comply to review and style guidelines

---
 src/core/crypto/aes_util.cpp             |  82 ++---
 src/core/crypto/aes_util.h               |  13 +-
 src/core/crypto/ctr_encryption_layer.cpp |  20 +-
 src/core/crypto/ctr_encryption_layer.h   |  15 +-
 src/core/crypto/encryption_layer.cpp     |   4 +-
 src/core/crypto/encryption_layer.h       |   5 +-
 src/core/crypto/key_manager.cpp          | 376 +++++------------------
 src/core/crypto/key_manager.h            |  77 +++--
 src/core/file_sys/card_image.cpp         |  15 +-
 src/core/file_sys/content_archive.cpp    |  48 ++-
 src/core/file_sys/content_archive.h      |   5 +-
 src/core/file_sys/vfs.cpp                |   7 +-
 src/core/loader/xci.cpp                  |   7 +-
 src/core/loader/xci.h                    |   3 +-
 src/yuzu/main.cpp                        |  15 +-
 src/yuzu_cmd/yuzu.cpp                    |  13 -
 16 files changed, 223 insertions(+), 482 deletions(-)

diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index a9646e52f3..4690af5f83 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -2,58 +2,69 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <mbedtls/cipher.h>
 #include "core/crypto/aes_util.h"
-#include "mbedtls/cipher.h"
+#include "core/crypto/key_manager.h"
 
 namespace Core::Crypto {
-static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR), "CTR mode is incorrect.");
-static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB), "ECB mode is incorrect.");
-static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS), "XTS mode is incorrect.");
 
-template<typename Key, size_t KeySize>
-Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) {
-    mbedtls_cipher_init(encryption_context.get());
-    mbedtls_cipher_init(decryption_context.get());
+static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR),
+              "CTR has incorrect value.");
+static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB),
+              "ECB has incorrect value.");
+static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS),
+              "XTS has incorrect value.");
+
+// Structure to hide mbedtls types from header file
+struct CipherContext {
+    mbedtls_cipher_context_t encryption_context;
+    mbedtls_cipher_context_t decryption_context;
+};
+
+template <typename Key, size_t KeySize>
+Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode)
+    : ctx(std::make_unique<CipherContext>()) {
+    mbedtls_cipher_init(&ctx->encryption_context);
+    mbedtls_cipher_init(&ctx->decryption_context);
 
     ASSERT_MSG((mbedtls_cipher_setup(
-            encryption_context.get(),
-            mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
-                mbedtls_cipher_setup(decryption_context.get(),
-                                     mbedtls_cipher_info_from_type(
-                                             static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
+                    &ctx->encryption_context,
+                    mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) ||
+                mbedtls_cipher_setup(
+                    &ctx->decryption_context,
+                    mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0,
                "Failed to initialize mbedtls ciphers.");
 
     ASSERT(
-            !mbedtls_cipher_setkey(encryption_context.get(), key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
+        !mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT));
     ASSERT(
-            !mbedtls_cipher_setkey(decryption_context.get(), key.data(), KeySize * 8, MBEDTLS_DECRYPT));
+        !mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT));
     //"Failed to set key on mbedtls ciphers.");
 }
 
-template<typename Key, size_t KeySize>
+template <typename Key, size_t KeySize>
 AESCipher<Key, KeySize>::~AESCipher() {
-    mbedtls_cipher_free(encryption_context.get());
-    mbedtls_cipher_free(decryption_context.get());
+    mbedtls_cipher_free(&ctx->encryption_context);
+    mbedtls_cipher_free(&ctx->decryption_context);
 }
 
-template<typename Key, size_t KeySize>
+template <typename Key, size_t KeySize>
 void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) {
-    ASSERT_MSG((mbedtls_cipher_set_iv(encryption_context.get(), iv.data(), iv.size()) ||
-                mbedtls_cipher_set_iv(decryption_context.get(), iv.data(), iv.size())) == 0,
+    ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) ||
+                mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0,
                "Failed to set IV on mbedtls ciphers.");
 }
 
-template<typename Key, size_t KeySize>
-void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op)  {
+template <typename Key, size_t KeySize>
+void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) {
     size_t written = 0;
 
-    const auto context = op == Op::Encrypt ? encryption_context.get() : decryption_context.get();
+    const auto context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context;
 
     mbedtls_cipher_reset(context);
 
     if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) {
-        mbedtls_cipher_update(context, src, size,
-                              dest, &written);
+        mbedtls_cipher_update(context, src, size, dest, &written);
         if (written != size)
             LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
                         size, written);
@@ -62,11 +73,9 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
 
         for (size_t offset = 0; offset < size; offset += block_size) {
             auto length = std::min<size_t>(block_size, size - offset);
-            mbedtls_cipher_update(context, src + offset, length,
-                                  dest + offset, &written);
+            mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
             if (written != length)
-                LOG_WARNING(Crypto,
-                            "Not all data was decrypted requested={:016X}, actual={:016X}.",
+                LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
                             length, written);
         }
     }
@@ -74,9 +83,9 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
     mbedtls_cipher_finish(context, nullptr, nullptr);
 }
 
-template<typename Key, size_t KeySize>
-void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size,
-                                           Op op) {
+template <typename Key, size_t KeySize>
+void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
+                                           size_t sector_size, Op op) {
     if (size % sector_size > 0) {
         LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
         return;
@@ -84,12 +93,11 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest,
 
     for (size_t i = 0; i < size; i += sector_size) {
         SetIV(CalculateNintendoTweak(sector_id++));
-        Transcode<u8, u8>(src + i, sector_size,
-                          dest + i, op);
+        Transcode<u8, u8>(src + i, sector_size, dest + i, op);
     }
 }
 
-template<typename Key, size_t KeySize>
+template <typename Key, size_t KeySize>
 std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id) {
     std::vector<u8> out(0x10);
     for (size_t i = 0xF; i <= 0xF; --i) {
@@ -101,4 +109,4 @@ std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id
 
 template class AESCipher<Key128>;
 template class AESCipher<Key256>;
-}
\ No newline at end of file
+} // namespace Core::Crypto
\ No newline at end of file
diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index 5c09718b22..fa77d55601 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -20,7 +20,7 @@ enum class Op {
     Decrypt,
 };
 
-struct mbedtls_cipher_context_t;
+struct CipherContext;
 
 template <typename Key, size_t KeySize = sizeof(Key)>
 class AESCipher {
@@ -44,15 +44,16 @@ public:
     template <typename Source, typename Dest>
     void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id,
                       size_t sector_size, Op op) {
-        XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id, sector_size, op);
+        XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id,
+                     sector_size, op);
     }
 
-    void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, Op op);
+    void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size,
+                      Op op);
 
 private:
-    std::unique_ptr<mbedtls_cipher_context_t> encryption_context;
-    std::unique_ptr<mbedtls_cipher_context_t> decryption_context;
+    std::unique_ptr<CipherContext> ctx;
 
     static std::vector<u8> CalculateNintendoTweak(size_t sector_id);
 };
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 6a0d396ed1..5dbc257e5e 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -6,7 +6,8 @@
 #include "common/assert.h"
 #include "core/crypto/ctr_encryption_layer.h"
 
-namespace Crypto {
+namespace Core::Crypto {
+
 CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset)
     : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR),
       iv(16, 0) {}
@@ -21,29 +22,28 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
         std::vector<u8> raw = base->ReadBytes(length, offset);
         if (raw.size() != length)
             return Read(data, raw.size(), offset);
-        cipher.Transcode(raw.data(), length, data, Op::DECRYPT);
+        cipher.Transcode(raw.data(), length, data, Op::Decrypt);
         return length;
     }
 
     // offset does not fall on block boundary (0x10)
     std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset);
     UpdateIV(base_offset + offset - sector_offset);
-    cipher.Transcode(block.data(), block.size(), block.data(), Op::DECRYPT);
+    cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt);
     size_t read = 0x10 - sector_offset;
 
     if (length + sector_offset < 0x10) {
-        memcpy_s(data, length, block.data() + sector_offset, std::min<u64>(length, read));
+        memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
         return read;
     }
 
-    memcpy_s(data, length, block.data() + sector_offset, read);
+    memcpy(data, block.data() + sector_offset, read);
     return read + Read(data + read, length - read, offset + read);
 }
 
-void CTREncryptionLayer::SetIV(std::vector<u8> iv_) {
-    const auto length = std::min<size_t>(iv_.size(), iv.size());
-    for (size_t i = 0; i < length; ++i)
-        iv[i] = iv_[i];
+void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) {
+    const auto length = std::min(iv_.size(), iv.size());
+    iv.assign(iv_.cbegin(), iv_.cbegin() + length);
 }
 
 void CTREncryptionLayer::UpdateIV(size_t offset) const {
@@ -54,4 +54,4 @@ void CTREncryptionLayer::UpdateIV(size_t offset) const {
     }
     cipher.SetIV(iv);
 }
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
index fe53e714b1..697d7c6a56 100644
--- a/src/core/crypto/ctr_encryption_layer.h
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -4,19 +4,20 @@
 
 #pragma once
 
-#include "aes_util.h"
-#include "encryption_layer.h"
-#include "key_manager.h"
+#include "core/crypto/aes_util.h"
+#include "core/crypto/encryption_layer.h"
+#include "core/crypto/key_manager.h"
 
-namespace Crypto {
+namespace Core::Crypto {
 
 // Sits on top of a VirtualFile and provides CTR-mode AES decription.
-struct CTREncryptionLayer : public EncryptionLayer {
+class CTREncryptionLayer : public EncryptionLayer {
+public:
     CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset);
 
     size_t Read(u8* data, size_t length, size_t offset) const override;
 
-    void SetIV(std::vector<u8> iv);
+    void SetIV(const std::vector<u8>& iv);
 
 private:
     size_t base_offset;
@@ -28,4 +29,4 @@ private:
     void UpdateIV(size_t offset) const;
 };
 
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp
index 4e243051e2..4204527e33 100644
--- a/src/core/crypto/encryption_layer.cpp
+++ b/src/core/crypto/encryption_layer.cpp
@@ -4,7 +4,7 @@
 
 #include "core/crypto/encryption_layer.h"
 
-namespace Crypto {
+namespace Core::Crypto {
 
 EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {}
 
@@ -39,4 +39,4 @@ size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) {
 bool EncryptionLayer::Rename(std::string_view name) {
     return base->Rename(name);
 }
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
index 2312870d83..84f11bf5e1 100644
--- a/src/core/crypto/encryption_layer.h
+++ b/src/core/crypto/encryption_layer.h
@@ -3,9 +3,10 @@
 // Refer to the license.txt file included.
 
 #pragma once
+
 #include "core/file_sys/vfs.h"
 
-namespace Crypto {
+namespace Core::Crypto {
 
 // Basically non-functional class that implements all of the methods that are irrelevant to an
 // EncryptionLayer. Reduces duplicate code.
@@ -27,4 +28,4 @@ protected:
     FileSys::VirtualFile base;
 };
 
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 05c6a70a24..ea20bc31ba 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -5,238 +5,15 @@
 #include <fstream>
 #include <locale>
 #include <sstream>
+#include <mbedtls/sha256.h>
 #include "common/assert.h"
+#include "common/common_paths.h"
+#include "common/file_util.h"
 #include "common/logging/log.h"
 #include "core/crypto/key_manager.h"
-#include "mbedtls/sha256.h"
+#include "core/settings.h"
 
-namespace Crypto {
-KeyManager keys = {};
-
-std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_prod = {
-    {{S128KeyType::MASTER, 0, 0},
-     "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32},
-    {{S128KeyType::MASTER, 1, 0},
-     "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32},
-    {{S128KeyType::MASTER, 2, 0},
-     "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32},
-    {{S128KeyType::MASTER, 3, 0},
-     "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32},
-    {{S128KeyType::MASTER, 4, 0},
-     "75ff1d95d26113550ee6fcc20acb58e97edeb3a2ff52543ed5aec63bdcc3da50"_array32},
-    {{S128KeyType::PACKAGE1, 0, 0},
-     "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40"_array32},
-    {{S128KeyType::PACKAGE1, 1, 0},
-     "4A11DA019D26470C9B805F1721364830DC0096DD66EAC453B0D14455E5AF5CF8"_array32},
-    {{S128KeyType::PACKAGE1, 2, 0},
-     "CCA867360B3318246FBF0B8A86473176ED486DFE229772B941A02E84D50A3155"_array32},
-    {{S128KeyType::PACKAGE1, 3, 0},
-     "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F"_array32},
-    {{S128KeyType::PACKAGE1, 4, 0},
-     "28ae73d6ae8f7206fca549e27097714e599df1208e57099416ff429b71370162"_array32},
-    {{S128KeyType::PACKAGE2, 0, 0},
-     "94D6F38B9D0456644E21DFF4707D092B70179B82D1AA2F5B6A76B8F9ED948264"_array32},
-    {{S128KeyType::PACKAGE2, 1, 0},
-     "7794F24FA879D378FEFDC8776B949B88AD89386410BE9025D463C619F1530509"_array32},
-    {{S128KeyType::PACKAGE2, 2, 0},
-     "5304BDDE6AC8E462961B5DB6E328B1816D245D36D6574BB78938B74D4418AF35"_array32},
-    {{S128KeyType::PACKAGE2, 3, 0},
-     "BE1E52C4345A979DDD4924375B91C902052C2E1CF8FBF2FAA42E8F26D5125B60"_array32},
-    {{S128KeyType::PACKAGE2, 4, 0},
-     "631b45d349ab8f76a050fe59512966fb8dbaf0755ef5b6903048bf036cfa611e"_array32},
-    {{S128KeyType::TITLEKEK, 0, 0},
-     "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
-    {{S128KeyType::TITLEKEK, 1, 0},
-     "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
-    {{S128KeyType::TITLEKEK, 2, 0},
-     "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
-    {{S128KeyType::TITLEKEK, 3, 0},
-     "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
-    {{S128KeyType::TITLEKEK, 4, 0},
-     "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
-    {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
-     "46cccf288286e31c931379de9efa288c95c9a15e40b00a4c563a8be244ece515"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
-     "592957F44FE5DB5EC6B095F568910E31A226D3B7FE42D64CFB9CE4051E90AEB6"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
-     "C2252A0FBF9D339ABC3D681351D00452F926E7CA0C6CA85F659078DE3FA647F3"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
-     "7C7722824B2F7C4938C40F3EA93E16CB69D3285EB133490EF8ECCD2C4B52DF41"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
-     "AFBB8EBFB2094F1CF71E330826AE06D64414FCA128C464618DF30EED92E62BE6"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
-     "5dc10eb81918da3f2fa90f69c8542511963656cfb31fb7c779581df8faf1f2f5"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "AA2C65F0E27F730807A13F2ED5B99BE5183165B87C50B6ED48F5CAC2840687EB"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "860185F2313A14F7006A029CB21A52750E7718C1E94FFB98C0AE2207D1A60165"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "7283FB1EFBD42438DADF363FDB776ED355C98737A2AAE75D0E9283CE1C12A2E4"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "9881C2D3AB70B14C8AA12016FC73ADAD93C6AD9FB59A9ECAD312B6F89E2413EC"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "eaa6a8d242b89e174928fa9549a0f66ec1562e2576fac896f438a2b3c1fb6005"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
-     "194CF6BD14554DA8D457E14CBFE04E55C8FB8CA52E0AFB3D7CB7084AE435B801"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
-     "CE1DB7BB6E5962384889DB7A396AFD614F82F69DC38A33D2DEAF47F3E4B964B7"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
-     "42238DE5685DEF4FDE7BE42C0097CEB92447006386D6B5D5AAA2C9AFD2E28422"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
-     "1F6847F268E9D9C5D1AD4D7E226A63B833BF02071446957A962EF065521879C1"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
-     "644007f9913c3602399d4d75cc34faeb7f1faad18b23e34187b16fdc45f4980f"_array32},
-};
-
-std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_prod = {
-    {{S256KeyType::HEADER, 0, 0},
-     "8E03DE24818D96CE4F2A09B43AF979E679974F7570713A61EED8B314864A11D5"_array32},
-    {{S256KeyType::SD_SAVE, 0, 0},
-     "13020ee72d0f8b8f9112dc738b829fdb017102499a7c2259b52aeefc0a273f5c"_array32},
-    {{S256KeyType::SD_NCA, 0, 0},
-     "8a1c05b4f88bae5b04d77f632e6acfc8893c4a05fd701f53585daafc996b532a"_array32},
-};
-
-// TODO(DarkLordZach): Find missing hashes for dev keys.
-
-std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> KeyManager::s128_hash_dev = {
-    {{S128KeyType::MASTER, 0, 0},
-     "779dd8b533a2fb670f27b308cb8d0151c4a107568b817429172b7f80aa592c25"_array32},
-    {{S128KeyType::MASTER, 1, 0},
-     "0175c8bc49771576f75527be719098db4ebaf77707206749415663aa3a9ea9cc"_array32},
-    {{S128KeyType::MASTER, 2, 0},
-     "4f0b4d724e5a8787268157c7ce0767c26d2e2021832aa7020f306d6e260eea42"_array32},
-    {{S128KeyType::MASTER, 3, 0},
-     "7b5a29586c1f84f66fbfabb94518fc45408bb8e5445253d063dda7cfef2a818c"_array32},
-    {{S128KeyType::MASTER, 4, 0},
-     "87a61dbb05a8755de7fe069562aab38ebfb266c9eb835f09fa62dacc89c98341"_array32},
-    {{S128KeyType::PACKAGE1, 0, 0},
-     "166510bc63ae50391ebe4ee4ff90ca31cd0e2dd0ff6be839a2f573ec146cc23a"_array32},
-    {{S128KeyType::PACKAGE1, 1, 0},
-     "f74cd01b86743139c920ec54a8116c669eea805a0be1583e13fc5bc8de68645b"_array32},
-    {{S128KeyType::PACKAGE1, 2, 0},
-     "d0cdecd513bb6aa3d9dc6244c977dc8a5a7ea157d0a8747d79e7581146e1f768"_array32},
-    {{S128KeyType::PACKAGE1, 3, 0},
-     "aa39394d626b3b79f5b7ccc07378b5996b6d09bf0eb6771b0b40c9077fbfde8c"_array32},
-    {{S128KeyType::PACKAGE1, 4, 0},
-     "8f4754b8988c0e673fc2bbea0534cdd6075c815c9270754ae980aef3e4f0a508"_array32},
-    {{S128KeyType::PACKAGE2, 0, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::PACKAGE2, 1, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::PACKAGE2, 2, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::PACKAGE2, 3, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::PACKAGE2, 4, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::TITLEKEK, 0, 0},
-     "C2FA30CAC6AE1680466CB54750C24550E8652B3B6F38C30B49DADF067B5935E9"_array32},
-    {{S128KeyType::TITLEKEK, 1, 0},
-     "0D6B8F3746AD910D36438A859C11E8BE4310112425D63751D09B5043B87DE598"_array32},
-    {{S128KeyType::TITLEKEK, 2, 0},
-     "D09E18D3DB6BC7393536896F728528736FBEFCDD15C09D9D612FDE5C7BDCD821"_array32},
-    {{S128KeyType::TITLEKEK, 3, 0},
-     "47C6F9F7E99BB1F56DCDC93CDBD340EA82DCCD74DD8F3535ADA20ECF79D438ED"_array32},
-    {{S128KeyType::TITLEKEK, 4, 0},
-     "128610de8424cb29e08f9ee9a81c9e6ffd3c6662854aad0c8f937e0bcedc4d88"_array32},
-    {{S128KeyType::ETICKET_RSA_KEK, 0, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-};
-
-std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> KeyManager::s256_hash_dev = {
-    {{S256KeyType::HEADER, 0, 0},
-     "ecde86a76e37ac4fd7591d3aa55c00cc77d8595fc27968052ec18a177d939060"_array32},
-    {{S256KeyType::SD_SAVE, 0, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-    {{S256KeyType::SD_NCA, 0, 0},
-     "0000000000000000000000000000000000000000000000000000000000000000"_array32},
-};
-
-std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
-    {"master_key_00", {S128KeyType::MASTER, 0, 0}},
-    {"master_key_01", {S128KeyType::MASTER, 1, 0}},
-    {"master_key_02", {S128KeyType::MASTER, 2, 0}},
-    {"master_key_03", {S128KeyType::MASTER, 3, 0}},
-    {"master_key_04", {S128KeyType::MASTER, 4, 0}},
-    {"package1_key_00", {S128KeyType::PACKAGE1, 0, 0}},
-    {"package1_key_01", {S128KeyType::PACKAGE1, 1, 0}},
-    {"package1_key_02", {S128KeyType::PACKAGE1, 2, 0}},
-    {"package1_key_03", {S128KeyType::PACKAGE1, 3, 0}},
-    {"package1_key_04", {S128KeyType::PACKAGE1, 4, 0}},
-    {"package2_key_00", {S128KeyType::PACKAGE2, 0, 0}},
-    {"package2_key_01", {S128KeyType::PACKAGE2, 1, 0}},
-    {"package2_key_02", {S128KeyType::PACKAGE2, 2, 0}},
-    {"package2_key_03", {S128KeyType::PACKAGE2, 3, 0}},
-    {"package2_key_04", {S128KeyType::PACKAGE2, 4, 0}},
-    {"titlekek_00", {S128KeyType::TITLEKEK, 0, 0}},
-    {"titlekek_01", {S128KeyType::TITLEKEK, 1, 0}},
-    {"titlekek_02", {S128KeyType::TITLEKEK, 2, 0}},
-    {"titlekek_03", {S128KeyType::TITLEKEK, 3, 0}},
-    {"titlekek_04", {S128KeyType::TITLEKEK, 4, 0}},
-    {"eticket_rsa_kek", {S128KeyType::ETICKET_RSA_KEK, 0, 0}},
-    {"key_area_key_application_00",
-     {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
-    {"key_area_key_application_01",
-     {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
-    {"key_area_key_application_02",
-     {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
-    {"key_area_key_application_03",
-     {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
-    {"key_area_key_application_04",
-     {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
-    {"key_area_key_ocean_00", {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
-    {"key_area_key_ocean_01", {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
-    {"key_area_key_ocean_02", {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
-    {"key_area_key_ocean_03", {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
-    {"key_area_key_ocean_04", {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
-    {"key_area_key_system_00",
-     {S128KeyType::KEY_AREA, 0, static_cast<u64>(KeyAreaKeyType::System)}},
-    {"key_area_key_system_01",
-     {S128KeyType::KEY_AREA, 1, static_cast<u64>(KeyAreaKeyType::System)}},
-    {"key_area_key_system_02",
-     {S128KeyType::KEY_AREA, 2, static_cast<u64>(KeyAreaKeyType::System)}},
-    {"key_area_key_system_03",
-     {S128KeyType::KEY_AREA, 3, static_cast<u64>(KeyAreaKeyType::System)}},
-    {"key_area_key_system_04",
-     {S128KeyType::KEY_AREA, 4, static_cast<u64>(KeyAreaKeyType::System)}},
-};
-
-std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
-    {"header_key", {S256KeyType::HEADER, 0, 0}},
-    {"sd_card_save_key", {S256KeyType::SD_SAVE, 0, 0}},
-    {"sd_card_nca_key", {S256KeyType::SD_NCA, 0, 0}},
-};
+namespace Core::Crypto {
 
 static u8 ToHexNibble(char c1) {
     if (c1 >= 65 && c1 <= 70)
@@ -271,8 +48,20 @@ std::array<u8, 32> operator""_array32(const char* str, size_t len) {
     return HexStringToArray<32>(str);
 }
 
-void KeyManager::SetValidationMode(bool dev) {
-    dev_mode = dev;
+KeyManager::KeyManager() {
+    // Initialize keys
+    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
+    if (Settings::values.use_dev_keys) {
+        dev_mode = true;
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
+            LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
+    } else {
+        dev_mode = false;
+        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
+            LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
+    }
+    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
+        LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
 }
 
 void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
@@ -299,7 +88,7 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
             auto rights_id_raw = HexStringToArray<16>(out[0]);
             u128 rights_id = *reinterpret_cast<std::array<u64, 2>*>(&rights_id_raw);
             Key128 key = HexStringToArray<16>(out[1]);
-            SetKey(S128KeyType::TITLEKEY, key, rights_id[1], rights_id[0]);
+            SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
         } else {
             std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
             if (s128_file_id.find(out[0]) != s128_file_id.end()) {
@@ -315,24 +104,24 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
     }
 }
 
-bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) {
+bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
     return s128_keys.find({id, field1, field2}) != s128_keys.end();
 }
 
-bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) {
+bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const {
     return s256_keys.find({id, field1, field2}) != s256_keys.end();
 }
 
-Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) {
+Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const {
     if (!HasKey(id, field1, field2))
         return {};
-    return s128_keys[{id, field1, field2}];
+    return s128_keys.at({id, field1, field2});
 }
 
-Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) {
+Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
     if (!HasKey(id, field1, field2))
         return {};
-    return s256_keys[{id, field1, field2}];
+    return s256_keys.at({id, field1, field2});
 }
 
 void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -343,68 +132,53 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
     s256_keys[{id, field1, field2}] = key;
 }
 
-bool KeyManager::ValidateKey(S128KeyType key, u64 field1, u64 field2) {
-    auto& hash = dev_mode ? s128_hash_dev : s128_hash_prod;
+std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
+    {"master_key_00", {S128KeyType::Master, 0, 0}},
+    {"master_key_01", {S128KeyType::Master, 1, 0}},
+    {"master_key_02", {S128KeyType::Master, 2, 0}},
+    {"master_key_03", {S128KeyType::Master, 3, 0}},
+    {"master_key_04", {S128KeyType::Master, 4, 0}},
+    {"package1_key_00", {S128KeyType::Package1, 0, 0}},
+    {"package1_key_01", {S128KeyType::Package1, 1, 0}},
+    {"package1_key_02", {S128KeyType::Package1, 2, 0}},
+    {"package1_key_03", {S128KeyType::Package1, 3, 0}},
+    {"package1_key_04", {S128KeyType::Package1, 4, 0}},
+    {"package2_key_00", {S128KeyType::Package2, 0, 0}},
+    {"package2_key_01", {S128KeyType::Package2, 1, 0}},
+    {"package2_key_02", {S128KeyType::Package2, 2, 0}},
+    {"package2_key_03", {S128KeyType::Package2, 3, 0}},
+    {"package2_key_04", {S128KeyType::Package2, 4, 0}},
+    {"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
+    {"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
+    {"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
+    {"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
+    {"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
+    {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
+    {"key_area_key_application_00",
+     {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_01",
+     {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_02",
+     {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_03",
+     {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_application_04",
+     {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
+    {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
+    {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
+    {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
+};
 
-    KeyIndex<S128KeyType> id = {key, field1, field2};
-    if (key == S128KeyType::SD_SEED || key == S128KeyType::TITLEKEY ||
-        hash.find(id) == hash.end()) {
-        LOG_WARNING(Crypto, "Could not validate [{}]", id.DebugInfo());
-        return true;
-    }
-
-    if (!HasKey(key, field1, field2)) {
-        LOG_CRITICAL(Crypto,
-                     "System has requested validation of [{}], but user has not added it. Add this "
-                     "key to use functionality.",
-                     id.DebugInfo());
-        return false;
-    }
-
-    SHA256Hash key_hash{};
-    const auto a_key = GetKey(key, field1, field2);
-    mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
-    if (key_hash != hash[id]) {
-        LOG_CRITICAL(Crypto,
-                     "The hash of the provided key for [{}] does not match the one on file. This "
-                     "means you probably have an incorrect key. If you believe this to be in "
-                     "error, contact the yuzu devs.",
-                     id.DebugInfo());
-        return false;
-    }
-
-    return true;
-}
-
-bool KeyManager::ValidateKey(S256KeyType key, u64 field1, u64 field2) {
-    auto& hash = dev_mode ? s256_hash_dev : s256_hash_prod;
-
-    KeyIndex<S256KeyType> id = {key, field1, field2};
-    if (hash.find(id) == hash.end()) {
-        LOG_ERROR(Crypto, "Could not validate [{}]", id.DebugInfo());
-        return true;
-    }
-
-    if (!HasKey(key, field1, field2)) {
-        LOG_ERROR(Crypto,
-                  "System has requested validation of [{}], but user has not added it. Add this "
-                  "key to use functionality.",
-                  id.DebugInfo());
-        return false;
-    }
-
-    SHA256Hash key_hash{};
-    const auto a_key = GetKey(key, field1, field2);
-    mbedtls_sha256(a_key.data(), a_key.size(), key_hash.data(), 0);
-    if (key_hash != hash[id]) {
-        LOG_CRITICAL(Crypto,
-                     "The hash of the provided key for [{}] does not match the one on file. This "
-                     "means you probably have an incorrect key. If you believe this to be in "
-                     "error, contact the yuzu devs.",
-                     id.DebugInfo());
-        return false;
-    }
-
-    return true;
-}
-} // namespace Crypto
+std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
+    {"header_key", {S256KeyType::Header, 0, 0}},
+    {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}},
+    {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}},
+};
+} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index b892a83f2c..e04f1d49f5 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -3,36 +3,37 @@
 // Refer to the license.txt file included.
 
 #pragma once
+
 #include <array>
 #include <unordered_map>
 #include <vector>
 #include <fmt/format.h>
 #include "common/common_types.h"
 
-namespace Crypto {
+namespace Core::Crypto {
 
-typedef std::array<u8, 0x10> Key128;
-typedef std::array<u8, 0x20> Key256;
-typedef std::array<u8, 0x20> SHA256Hash;
+using Key128 = std::array<u8, 0x10>;
+using Key256 = std::array<u8, 0x20>;
+using SHA256Hash = std::array<u8, 0x20>;
 
 static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
 static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
 
 enum class S256KeyType : u64 {
-    HEADER,  //
-    SD_SAVE, //
-    SD_NCA,  //
+    Header, //
+    SDSave, //
+    SDNCA,  //
 };
 
 enum class S128KeyType : u64 {
-    MASTER,          // f1=crypto revision
-    PACKAGE1,        // f1=crypto revision
-    PACKAGE2,        // f1=crypto revision
-    TITLEKEK,        // f1=crypto revision
-    ETICKET_RSA_KEK, //
-    KEY_AREA,        // f1=crypto revision f2=type {app, ocean, system}
-    SD_SEED,         //
-    TITLEKEY,        // f1=rights id LSB f2=rights id MSB
+    Master,        // f1=crypto revision
+    Package1,      // f1=crypto revision
+    Package2,      // f1=crypto revision
+    Titlekek,      // f1=crypto revision
+    ETicketRSAKek, //
+    KeyArea,       // f1=crypto revision f2=type {app, ocean, system}
+    SDSeed,        //
+    Titlekey,      // f1=rights id LSB f2=rights id MSB
 };
 
 enum class KeyAreaKeyType : u8 {
@@ -47,7 +48,7 @@ struct KeyIndex {
     u64 field1;
     u64 field2;
 
-    std::string DebugInfo() {
+    std::string DebugInfo() const {
         u8 key_size = 16;
         if (std::is_same_v<KeyType, S256KeyType>)
             key_size = 32;
@@ -60,15 +61,20 @@ struct KeyIndex {
 
 template <typename KeyType>
 bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
-    return lhs.type == rhs.type && lhs.field1 == rhs.field1 && lhs.field2 == rhs.field2;
+    return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2);
 }
 
-} // namespace Crypto
+template <typename KeyType>
+bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
+    return !operator==(lhs, rhs);
+}
+
+} // namespace Core::Crypto
 
 namespace std {
 template <typename KeyType>
-struct hash<Crypto::KeyIndex<KeyType>> {
-    size_t operator()(const Crypto::KeyIndex<KeyType>& k) const {
+struct hash<Core::Crypto::KeyIndex<KeyType>> {
+    size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const {
         using std::hash;
 
         return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
@@ -77,41 +83,32 @@ struct hash<Crypto::KeyIndex<KeyType>> {
 };
 } // namespace std
 
-namespace Crypto {
+namespace Core::Crypto {
 
 std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
 std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
 
-struct KeyManager {
-    void SetValidationMode(bool dev);
-    void LoadFromFile(std::string_view filename, bool is_title_keys);
+class KeyManager {
+public:
+    KeyManager();
 
-    bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
-    bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
+    bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
+    bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
 
-    Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0);
-    Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0);
+    Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
+    Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
 
     void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
     void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
 
-    bool ValidateKey(S128KeyType key, u64 field1 = 0, u64 field2 = 0);
-    bool ValidateKey(S256KeyType key, u64 field1 = 0, u64 field2 = 0);
-
 private:
     std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
 
-    bool dev_mode = false;
+    bool dev_mode;
+    void LoadFromFile(std::string_view filename, bool is_title_keys);
 
-    static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_prod;
-    static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_prod;
-    static std::unordered_map<KeyIndex<S128KeyType>, SHA256Hash> s128_hash_dev;
-    static std::unordered_map<KeyIndex<S256KeyType>, SHA256Hash> s256_hash_dev;
     static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
     static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
 };
-
-extern KeyManager keys;
-
-} // namespace Crypto
+} // namespace Core::Crypto
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 5ff09a362f..3c1dbf46ce 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -30,8 +30,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
         return;
     }
 
-    const static std::array<std::string, 0x4> partition_names = {"update", "normal", "secure",
-                                                                 "logo"};
+    static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure",
+                                                                     "logo"};
 
     for (XCIPartition partition :
          {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
@@ -93,12 +93,9 @@ VirtualDir XCI::GetLogoPartition() const {
 }
 
 std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
-    for (const auto& nca : ncas) {
-        if (nca->GetType() == type)
-            return nca;
-    }
-
-    return nullptr;
+    auto iter = std::find_if(ncas.begin(), ncas.end(),
+                             [type](std::shared_ptr<NCA> nca) { return nca->GetType() == type; });
+    return iter == ncas.end() ? nullptr : *iter;
 }
 
 VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
@@ -133,7 +130,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
         return Loader::ResultStatus::ErrorInvalidFormat;
     }
 
-    for (VirtualFile file : partitions[static_cast<size_t>(part)]->GetFiles()) {
+    for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) {
         if (file->GetExtension() != "nca")
             continue;
         auto nca = std::make_shared<NCA>(file);
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index add01974e2..952dc70683 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -9,10 +9,9 @@
 #include "core/crypto/aes_util.h"
 #include "core/crypto/ctr_encryption_layer.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/romfs.h"
 #include "core/file_sys/vfs_offset.h"
 #include "core/loader/loader.h"
-#include "mbedtls/cipher.h"
-#include "romfs.h"
 
 namespace FileSys {
 
@@ -77,7 +76,7 @@ bool IsValidNCA(const NCAHeader& header) {
     return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
 }
 
-Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
+Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
     u8 master_key_id = header.crypto_type;
     if (header.crypto_type_2 > master_key_id)
         master_key_id = header.crypto_type_2;
@@ -85,16 +84,12 @@ Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
         --master_key_id;
 
     std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
-    if (!Crypto::keys.ValidateKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index)) {
-        status = Loader::ResultStatus::ErrorEncrypted;
-        return {};
-    }
-    Crypto::AESCipher<Crypto::Key128> cipher(
-        Crypto::keys.GetKey(Crypto::S128KeyType::KEY_AREA, master_key_id, header.key_index),
-        Crypto::Mode::ECB);
-    cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Crypto::Op::DECRYPT);
+    Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
+        keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
+        Core::Crypto::Mode::ECB);
+    cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt);
 
-    Crypto::Key128 out;
+    Core::Crypto::Key128 out;
     if (type == NCASectionCryptoType::XTS)
         std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
     else if (type == NCASectionCryptoType::CTR)
@@ -102,8 +97,8 @@ Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
     else
         LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
                      static_cast<u8>(type));
-
-    u128 out_128 = *reinterpret_cast<u128*>(&out);
+    u128 out_128{};
+    memcpy(out_128.data(), out.data(), 16);
     LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
               master_key_id, header.key_index, out_128[1], out_128[0]);
 
@@ -121,9 +116,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_o
     case NCASectionCryptoType::CTR:
         LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
         {
-            auto out = std::make_shared<Crypto::CTREncryptionLayer>(
+            auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
                 std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset);
-            std::vector<u8> iv(16, 0);
+            std::vector<u8> iv(16);
             for (u8 i = 0; i < 8; ++i)
                 iv[i] = header.raw.section_ctr[0x8 - i - 1];
             out->SetIV(iv);
@@ -146,13 +141,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
 
     if (!IsValidNCA(header)) {
         NCAHeader dec_header{};
-        if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
-            status = Loader::ResultStatus::ErrorEncrypted;
-            return;
-        }
-        Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
-                                                 Crypto::Mode::XTS);
-        cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, Crypto::Op::DECRYPT);
+        Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+            keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
+        cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
+                            Core::Crypto::Op::Decrypt);
         if (IsValidNCA(dec_header)) {
             header = dec_header;
             encrypted = true;
@@ -171,14 +163,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
 
     if (encrypted) {
         auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
-        if (!Crypto::keys.ValidateKey(Crypto::S256KeyType::HEADER)) {
-            status = Loader::ResultStatus::ErrorEncrypted;
-            return;
-        }
-        Crypto::AESCipher<Crypto::Key256> cipher(Crypto::keys.GetKey(Crypto::S256KeyType::HEADER),
-                                                 Crypto::Mode::XTS);
+        Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+            keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
         cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
-                            Crypto::Op::DECRYPT);
+                            Core::Crypto::Op::Decrypt);
     } else {
         file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
     }
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index d9ad3bf7ed..153142b06a 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -9,12 +9,12 @@
 #include <string>
 #include <vector>
 
-#include "core/loader/loader.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/partition_filesystem.h"
+#include "core/loader/loader.h"
 
 namespace FileSys {
 enum class NCAContentType : u8 {
@@ -107,7 +107,8 @@ private:
 
     bool encrypted;
 
-    Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
+    Core::Crypto::KeyManager keys;
+    Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
 
     VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
 };
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index 57d0aeb854..dae1c16ef0 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -297,10 +297,9 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block
 
         if (f1_vs != f2_vs)
             return false;
-        for (size_t j = 0; j < f1_vs; ++j) {
-            if (f1_v[j] != f2_v[j])
-                return false;
-        }
+        auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end());
+        if (iters.first != f1_v.end() && iters.second != f2_v.end())
+            return false;
     }
 
     return true;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 9759e33d1e..b4de5bd169 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -28,14 +28,17 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
       nca_loader(std::make_unique<AppLoader_NCA>(
           xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {}
 
+AppLoader_XCI::~AppLoader_XCI() = default;
+
 FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
     FileSys::XCI xci(file);
 
     if (xci.GetStatus() == ResultStatus::Success &&
         xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr &&
         AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) ==
-            FileType::NCA)
+            FileType::NCA) {
         return FileType::XCI;
+    }
 
     return FileType::Error;
 }
@@ -62,6 +65,4 @@ ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
     return nca_loader->ReadProgramId(out_program_id);
 }
 
-AppLoader_XCI::~AppLoader_XCI() = default;
-
 } // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index a9cee1ca35..2a09caa5f8 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -13,6 +13,7 @@ namespace Loader {
 class AppLoader_XCI final : public AppLoader {
 public:
     explicit AppLoader_XCI(FileSys::VirtualFile file);
+    ~AppLoader_XCI();
 
     /**
      * Returns the type of the file
@@ -30,8 +31,6 @@ public:
     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
     ResultStatus ReadProgramId(u64& out_program_id) override;
 
-    ~AppLoader_XCI();
-
 private:
     FileSys::ProgramMetadata metadata;
 
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index d3523e6ac2..c274e5e2bd 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -13,7 +13,6 @@
 #include <QMessageBox>
 #include <QtGui>
 #include <QtWidgets>
-#include <core/crypto/key_manager.h>
 #include "common/common_paths.h"
 #include "common/logging/backend.h"
 #include "common/logging/filter.h"
@@ -24,6 +23,7 @@
 #include "common/scope_exit.h"
 #include "common/string_util.h"
 #include "core/core.h"
+#include "core/crypto/key_manager.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/loader/loader.h"
 #include "core/settings.h"
@@ -89,19 +89,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     ui.setupUi(this);
     statusBar()->hide();
 
-    // Initialize keys
-    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
-    if (Settings::values.use_dev_keys) {
-        Crypto::keys.SetValidationMode(true);
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
-            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
-    } else {
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
-            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
-    }
-    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
-        Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
-
     default_theme_paths = QIcon::themeSearchPaths();
     UpdateUITheme();
 
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 955e2ba140..97a8e13f01 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -73,19 +73,6 @@ static void InitializeLogging() {
 int main(int argc, char** argv) {
     Config config;
 
-    // Initialize keys
-    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
-    if (Settings::values.use_dev_keys) {
-        Crypto::keys.SetValidationMode(true);
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
-            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
-    } else {
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
-            Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
-    }
-    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
-        Crypto::keys.LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
-
     int option_index = 0;
     bool use_gdbstub = Settings::values.use_gdbstub;
     u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);

From cc8234fa89f1a7dcc2709e6d3be03f8458c841d4 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 29 Jul 2018 17:45:12 -0400
Subject: [PATCH 06/13] Use SHGetKnownFolderPath instead of SHGetFolderPathA

---
 src/common/file_util.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index a26702f54c..ed38c3ced2 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -738,10 +738,11 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
 
 std::string GetHactoolConfigurationPath() {
 #ifdef _WIN32
-    char path[MAX_PATH];
-    if (SHGetFolderPathA(nullptr, CSIDL_PROFILE, nullptr, 0, path) != S_OK)
+    PWSTR pw_local_path = nullptr;
+    if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK)
         return "";
-    std::string local_path = Common::StringFromFixedZeroTerminatedBuffer(path, MAX_PATH);
+    std::string local_path = Common::UTF16ToUTF8(pw_local_path);
+    CoTaskMemFree(pw_local_path);
     return local_path + "\\.switch";
 #else
     return GetHomeDirectory() + "/.switch";

From 150527ec194107f0ba5ea9bdef487782e64090ef Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 29 Jul 2018 18:42:04 -0400
Subject: [PATCH 07/13] Allow key loading from %YUZU_DIR%/keys in addition to
 ~/.switch

---
 src/common/common_paths.h       |  1 +
 src/common/file_util.cpp        |  1 +
 src/common/file_util.h          |  1 +
 src/core/crypto/key_manager.cpp | 25 ++++++++++++++++++-------
 src/core/crypto/key_manager.h   |  2 ++
 5 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 6799a357ae..df2ce80b13 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -32,6 +32,7 @@
 #define SDMC_DIR "sdmc"
 #define NAND_DIR "nand"
 #define SYSDATA_DIR "sysdata"
+#define KEYS_DIR "keys"
 #define LOG_DIR "log"
 
 // Filenames
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index ed38c3ced2..7aeda737f4 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -706,6 +706,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
         paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
         paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
         paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
+        paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
         // TODO: Put the logs in a better location for each OS
         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP);
     }
diff --git a/src/common/file_util.h b/src/common/file_util.h
index d530d86c9b..28697d527c 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -23,6 +23,7 @@ namespace FileUtil {
 enum class UserPath {
     CacheDir,
     ConfigDir,
+    KeysDir,
     LogDir,
     NANDDir,
     RootDir,
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index ea20bc31ba..dea092b5e2 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -12,6 +12,7 @@
 #include "common/logging/log.h"
 #include "core/crypto/key_manager.h"
 #include "core/settings.h"
+#include "key_manager.h"
 
 namespace Core::Crypto {
 
@@ -50,18 +51,17 @@ std::array<u8, 32> operator""_array32(const char* str, size_t len) {
 
 KeyManager::KeyManager() {
     // Initialize keys
-    std::string keys_dir = FileUtil::GetHactoolConfigurationPath();
+    std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
+    std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
     if (Settings::values.use_dev_keys) {
         dev_mode = true;
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "dev.keys"))
-            LoadFromFile(keys_dir + DIR_SEP + "dev.keys", false);
+        AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
     } else {
         dev_mode = false;
-        if (FileUtil::Exists(keys_dir + DIR_SEP + "prod.keys"))
-            LoadFromFile(keys_dir + DIR_SEP + "prod.keys", false);
+        AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
     }
-    if (FileUtil::Exists(keys_dir + DIR_SEP + "title.keys"))
-        LoadFromFile(keys_dir + DIR_SEP + "title.keys", true);
+
+    AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
 }
 
 void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
@@ -104,6 +104,17 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
     }
 }
 
+void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_,
+                                    std::string_view filename_, bool title) {
+    std::string dir1(dir1_);
+    std::string dir2(dir2_);
+    std::string filename(filename_);
+    if (FileUtil::Exists(dir1 + DIR_SEP + filename))
+        LoadFromFile(dir1 + DIR_SEP + filename, title);
+    else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
+        LoadFromFile(dir2 + DIR_SEP + filename, title);
+}
+
 bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
     return s128_keys.find({id, field1, field2}) != s128_keys.end();
 }
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index e04f1d49f5..a52ea4cb98 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -107,6 +107,8 @@ private:
 
     bool dev_mode;
     void LoadFromFile(std::string_view filename, bool is_title_keys);
+    void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename,
+                            bool title);
 
     static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
     static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;

From 03149d3e4a7f8038d9c88cbeb19dee25a39e0042 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 29 Jul 2018 19:00:09 -0400
Subject: [PATCH 08/13] Add missing includes and use const where applicable

---
 src/core/crypto/aes_util.h               |  7 +++++--
 src/core/crypto/ctr_encryption_layer.cpp |  7 +++----
 src/core/crypto/ctr_encryption_layer.h   |  1 +
 src/core/crypto/encryption_layer.h       |  3 ++-
 src/core/crypto/key_manager.cpp          | 13 ++++++++-----
 src/core/crypto/key_manager.h            |  7 ++++---
 src/core/file_sys/card_image.cpp         |  5 +++--
 src/core/file_sys/card_image.h           |  3 +++
 src/core/file_sys/content_archive.cpp    |  4 ++--
 src/core/file_sys/content_archive.h      | 11 ++++++-----
 src/core/loader/xci.h                    |  3 +++
 11 files changed, 40 insertions(+), 24 deletions(-)

diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h
index fa77d55601..5b0b02738e 100644
--- a/src/core/crypto/aes_util.h
+++ b/src/core/crypto/aes_util.h
@@ -4,11 +4,16 @@
 
 #pragma once
 
+#include <memory>
+#include <type_traits>
+#include <vector>
 #include "common/assert.h"
 #include "core/file_sys/vfs.h"
 
 namespace Core::Crypto {
 
+struct CipherContext;
+
 enum class Mode {
     CTR = 11,
     ECB = 2,
@@ -20,8 +25,6 @@ enum class Op {
     Decrypt,
 };
 
-struct CipherContext;
-
 template <typename Key, size_t KeySize = sizeof(Key)>
 class AESCipher {
     static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8.");
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 5dbc257e5e..106db02b39 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -2,7 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include <string.h>
+#include <cstring>
 #include "common/assert.h"
 #include "core/crypto/ctr_encryption_layer.h"
 
@@ -33,11 +33,10 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
     size_t read = 0x10 - sector_offset;
 
     if (length + sector_offset < 0x10) {
-        memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
+        std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
         return read;
     }
-
-    memcpy(data, block.data() + sector_offset, read);
+    std::memcpy(data, block.data() + sector_offset, read);
     return read + Read(data + read, length - read, offset + read);
 }
 
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h
index 697d7c6a56..11b8683c78 100644
--- a/src/core/crypto/ctr_encryption_layer.h
+++ b/src/core/crypto/ctr_encryption_layer.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <vector>
 #include "core/crypto/aes_util.h"
 #include "core/crypto/encryption_layer.h"
 #include "core/crypto/key_manager.h"
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h
index 84f11bf5e1..71bca1f23c 100644
--- a/src/core/crypto/encryption_layer.h
+++ b/src/core/crypto/encryption_layer.h
@@ -10,7 +10,8 @@ namespace Core::Crypto {
 
 // Basically non-functional class that implements all of the methods that are irrelevant to an
 // EncryptionLayer. Reduces duplicate code.
-struct EncryptionLayer : public FileSys::VfsFile {
+class EncryptionLayer : public FileSys::VfsFile {
+public:
     explicit EncryptionLayer(FileSys::VirtualFile base);
 
     size_t Read(u8* data, size_t length, size_t offset) const override = 0;
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index dea092b5e2..33633de7eb 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -2,9 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <array>
 #include <fstream>
 #include <locale>
 #include <sstream>
+#include <string_view>
 #include <mbedtls/sha256.h>
 #include "common/assert.h"
 #include "common/common_paths.h"
@@ -86,17 +88,18 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
 
         if (is_title_keys) {
             auto rights_id_raw = HexStringToArray<16>(out[0]);
-            u128 rights_id = *reinterpret_cast<std::array<u64, 2>*>(&rights_id_raw);
+            u128 rights_id{};
+            std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
             Key128 key = HexStringToArray<16>(out[1]);
             SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
         } else {
             std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
             if (s128_file_id.find(out[0]) != s128_file_id.end()) {
-                const auto index = s128_file_id[out[0]];
+                const auto index = s128_file_id.at(out[0]);
                 Key128 key = HexStringToArray<16>(out[1]);
                 SetKey(index.type, key, index.field1, index.field2);
             } else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
-                const auto index = s256_file_id[out[0]];
+                const auto index = s256_file_id.at(out[0]);
                 Key256 key = HexStringToArray<32>(out[1]);
                 SetKey(index.type, key, index.field1, index.field2);
             }
@@ -143,7 +146,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
     s256_keys[{id, field1, field2}] = key;
 }
 
-std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
+const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
     {"master_key_00", {S128KeyType::Master, 0, 0}},
     {"master_key_01", {S128KeyType::Master, 1, 0}},
     {"master_key_02", {S128KeyType::Master, 2, 0}},
@@ -187,7 +190,7 @@ std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id
     {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
 };
 
-std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
+const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
     {"header_key", {S256KeyType::Header, 0, 0}},
     {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}},
     {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}},
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index a52ea4cb98..28a560a3f5 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <type_traits>
 #include <unordered_map>
 #include <vector>
 #include <fmt/format.h>
@@ -50,7 +51,7 @@ struct KeyIndex {
 
     std::string DebugInfo() const {
         u8 key_size = 16;
-        if (std::is_same_v<KeyType, S256KeyType>)
+        if constexpr (std::is_same_v<KeyType, S256KeyType>)
             key_size = 32;
         return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size,
                            static_cast<u8>(type), field1, field2);
@@ -110,7 +111,7 @@ private:
     void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename,
                             bool title);
 
-    static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
-    static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
+    const static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
+    const static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
 };
 } // namespace Core::Crypto
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 3c1dbf46ce..c69812455b 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -93,8 +93,9 @@ VirtualDir XCI::GetLogoPartition() const {
 }
 
 std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
-    auto iter = std::find_if(ncas.begin(), ncas.end(),
-                             [type](std::shared_ptr<NCA> nca) { return nca->GetType() == type; });
+    const auto iter =
+        std::find_if(ncas.begin(), ncas.end(),
+                     [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
     return iter == ncas.end() ? nullptr : *iter;
 }
 
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index b765c8bc10..e089d737cb 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -4,10 +4,13 @@
 
 #pragma once
 
+#include <array>
 #include <vector>
+#include "common/common_types.h"
 #include "common/swap.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/vfs.h"
+#include "core/loader/loader.h"
 
 namespace FileSys {
 
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 952dc70683..9eceaa4c4b 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -76,7 +76,7 @@ bool IsValidNCA(const NCAHeader& header) {
     return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
 }
 
-Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
+Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
     u8 master_key_id = header.crypto_type;
     if (header.crypto_type_2 > master_key_id)
         master_key_id = header.crypto_type_2;
@@ -105,7 +105,7 @@ Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) {
     return out;
 }
 
-VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) {
+VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const {
     if (!encrypted)
         return in;
 
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 153142b06a..e68ab02358 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -17,6 +17,9 @@
 #include "core/loader/loader.h"
 
 namespace FileSys {
+
+union NCASectionHeader;
+
 enum class NCAContentType : u8 {
     Program = 0,
     Meta = 1,
@@ -61,8 +64,6 @@ struct NCAHeader {
 };
 static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
 
-union NCASectionHeader;
-
 inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
     // According to switchbrew, an exefs must only contain these two files:
     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
@@ -94,6 +95,9 @@ protected:
     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 
 private:
+    Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type) const;
+    VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const;
+
     std::vector<VirtualDir> dirs;
     std::vector<VirtualFile> files;
 
@@ -108,9 +112,6 @@ private:
     bool encrypted;
 
     Core::Crypto::KeyManager keys;
-    Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type);
-
-    VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
 };
 
 } // namespace FileSys
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 2a09caa5f8..0dbcfbdf83 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -4,7 +4,10 @@
 
 #pragma once
 
+#include <memory>
+#include "common/common_types.h"
 #include "core/file_sys/card_image.h"
+#include "core/loader/loader.h"
 #include "core/loader/nca.h"
 
 namespace Loader {

From a9c921a41dec63f76f80df1f0d5dc3be40fa80de Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 29 Jul 2018 20:47:33 -0400
Subject: [PATCH 09/13] Use ErrorEncrypted where applicable and fix no keys
 crash

---
 src/core/file_sys/card_image.cpp      |  1 +
 src/core/file_sys/content_archive.cpp | 43 ++++++++++++++++++---------
 src/core/file_sys/content_archive.h   |  4 +--
 src/core/loader/xci.cpp               |  4 +++
 4 files changed, 36 insertions(+), 16 deletions(-)

diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index c69812455b..395eea8ae0 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -45,6 +45,7 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
         status = result;
         return;
     }
+
     result = AddNCAFromPartition(XCIPartition::Update);
     if (result != Loader::ResultStatus::Success) {
         status = result;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 9eceaa4c4b..cb59c0a2bf 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -4,7 +4,7 @@
 
 #include <algorithm>
 #include <utility>
-
+#include <boost/optional.hpp>
 #include "common/logging/log.h"
 #include "core/crypto/aes_util.h"
 #include "core/crypto/ctr_encryption_layer.h"
@@ -76,13 +76,16 @@ bool IsValidNCA(const NCAHeader& header) {
     return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
 }
 
-Core::Crypto::Key128 NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
+boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
     u8 master_key_id = header.crypto_type;
     if (header.crypto_type_2 > master_key_id)
         master_key_id = header.crypto_type_2;
     if (master_key_id > 0)
         --master_key_id;
 
+    if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
+        return boost::none;
+
     std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
     Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
         keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index),
@@ -116,13 +119,16 @@ VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_o
     case NCASectionCryptoType::CTR:
         LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
         {
+            const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR);
+            if (key == boost::none)
+                return nullptr;
             auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
-                std::move(in), GetKeyAreaKey(NCASectionCryptoType::CTR), starting_offset);
+                std::move(in), key.value(), starting_offset);
             std::vector<u8> iv(16);
             for (u8 i = 0; i < 8; ++i)
                 iv[i] = header.raw.section_ctr[0x8 - i - 1];
             out->SetIV(iv);
-            return out;
+            return std::static_pointer_cast<VfsFile>(out);
         }
     case NCASectionCryptoType::XTS:
         // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption.
@@ -149,7 +155,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
             header = dec_header;
             encrypted = true;
         } else {
-            status = Loader::ResultStatus::ErrorInvalidFormat;
+            if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
+                status = Loader::ResultStatus::ErrorEncrypted;
+            else
+                status = Loader::ResultStatus::ErrorInvalidFormat;
             return;
         }
     }
@@ -179,23 +188,29 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
                 section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
             const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
-            files.emplace_back(
+            const auto dec =
                 Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
-                        romfs_offset));
-            romfs = files.back();
+                        romfs_offset);
+            if (dec != nullptr) {
+                files.emplace_back();
+                romfs = files.back();
+            }
         } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
                           MEDIA_OFFSET_MULTIPLIER) +
                          section.pfs0.pfs0_header_offset;
             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
                                                   header.section_tables[i].media_offset);
-            auto npfs = std::make_shared<PartitionFilesystem>(
-                Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset));
+            const auto dec =
+                Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
+            if (dec != nullptr) {
+                auto npfs = std::make_shared<PartitionFilesystem>(dec);
 
-            if (npfs->GetStatus() == Loader::ResultStatus::Success) {
-                dirs.emplace_back(npfs);
-                if (IsDirectoryExeFS(dirs.back()))
-                    exefs = dirs.back();
+                if (npfs->GetStatus() == Loader::ResultStatus::Success) {
+                    dirs.emplace_back(npfs);
+                    if (IsDirectoryExeFS(dirs.back()))
+                        exefs = dirs.back();
+                }
             }
         }
     }
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index e68ab02358..6492163b53 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -8,7 +8,7 @@
 #include <memory>
 #include <string>
 #include <vector>
-
+#include <boost/optional.hpp>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -95,7 +95,7 @@ protected:
     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 
 private:
-    Core::Crypto::Key128 GetKeyAreaKey(NCASectionCryptoType type) const;
+    boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
     VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const;
 
     std::vector<VirtualDir> dirs;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index b4de5bd169..74940fb831 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -48,6 +48,10 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
         return ResultStatus::ErrorAlreadyLoaded;
     }
 
+    if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) {
+        return ResultStatus::ErrorEncrypted;
+    }
+
     auto result = nca_loader->Load(process);
     if (result != ResultStatus::Success)
         return result;

From 9d59b96ef907e8be2560107485f7616a2234c4a8 Mon Sep 17 00:00:00 2001
From: Zach Hilman <DarkLordZach@users.noreply.github.com>
Date: Sun, 29 Jul 2018 21:00:50 -0400
Subject: [PATCH 10/13] Use static const instead of const static

---
 src/core/crypto/key_manager.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 28a560a3f5..c09a6197e6 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -111,7 +111,7 @@ private:
     void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename,
                             bool title);
 
-    const static std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
-    const static std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
+    static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
+    static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
 };
 } // namespace Core::Crypto

From 187d8e215fb157edaa9f3976bebba9a9a7ed103d Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 30 Jul 2018 12:46:23 -0400
Subject: [PATCH 11/13] Use more descriptive error codes and messages

---
 src/core/core.cpp                     | 12 ++++---
 src/core/core.h                       | 14 ++++----
 src/core/crypto/key_manager.cpp       | 27 +++++++++++---
 src/core/crypto/key_manager.h         |  2 ++
 src/core/file_sys/content_archive.cpp | 10 ++++--
 src/core/loader/loader.h              |  3 +-
 src/core/loader/xci.cpp               |  2 +-
 src/yuzu/main.cpp                     | 51 +++++++++++++++++++++------
 src/yuzu_cmd/yuzu.cpp                 | 14 +++++---
 9 files changed, 101 insertions(+), 34 deletions(-)

diff --git a/src/core/core.cpp b/src/core/core.cpp
index b7f4b45323..0ef6af3fe6 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -101,8 +101,10 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
                      static_cast<int>(system_mode.second));
 
         switch (system_mode.second) {
-        case Loader::ResultStatus::ErrorEncrypted:
-            return ResultStatus::ErrorLoader_ErrorEncrypted;
+        case Loader::ResultStatus::ErrorMissingKeys:
+            return ResultStatus::ErrorLoader_ErrorMissingKeys;
+        case Loader::ResultStatus::ErrorDecrypting:
+            return ResultStatus::ErrorLoader_ErrorDecrypting;
         case Loader::ResultStatus::ErrorInvalidFormat:
             return ResultStatus::ErrorLoader_ErrorInvalidFormat;
         case Loader::ResultStatus::ErrorUnsupportedArch:
@@ -126,8 +128,10 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
         System::Shutdown();
 
         switch (load_result) {
-        case Loader::ResultStatus::ErrorEncrypted:
-            return ResultStatus::ErrorLoader_ErrorEncrypted;
+        case Loader::ResultStatus::ErrorMissingKeys:
+            return ResultStatus::ErrorLoader_ErrorMissingKeys;
+        case Loader::ResultStatus::ErrorDecrypting:
+            return ResultStatus::ErrorLoader_ErrorDecrypting;
         case Loader::ResultStatus::ErrorInvalidFormat:
             return ResultStatus::ErrorLoader_ErrorInvalidFormat;
         case Loader::ResultStatus::ErrorUnsupportedArch:
diff --git a/src/core/core.h b/src/core/core.h
index c123fe4018..a90bff3df3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -43,12 +43,14 @@ public:
 
     /// Enumeration representing the return values of the System Initialize and Load process.
     enum class ResultStatus : u32 {
-        Success,                    ///< Succeeded
-        ErrorNotInitialized,        ///< Error trying to use core prior to initialization
-        ErrorGetLoader,             ///< Error finding the correct application loader
-        ErrorSystemMode,            ///< Error determining the system mode
-        ErrorLoader,                ///< Error loading the specified application
-        ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption
+        Success,                      ///< Succeeded
+        ErrorNotInitialized,          ///< Error trying to use core prior to initialization
+        ErrorGetLoader,               ///< Error finding the correct application loader
+        ErrorSystemMode,              ///< Error determining the system mode
+        ErrorLoader,                  ///< Error loading the specified application
+        ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be
+                                      ///< found.
+        ErrorLoader_ErrorDecrypting,  ///< Error loading the specified application due to encryption
         ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
                                         /// invalid format
         ErrorSystemFiles,               ///< Error in finding system files
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 33633de7eb..678ac57528 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -53,8 +53,8 @@ std::array<u8, 32> operator""_array32(const char* str, size_t len) {
 
 KeyManager::KeyManager() {
     // Initialize keys
-    std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
-    std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+    const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
+    const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
     if (Settings::values.use_dev_keys) {
         dev_mode = true;
         AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
@@ -109,9 +109,9 @@ void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) {
 
 void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_,
                                     std::string_view filename_, bool title) {
-    std::string dir1(dir1_);
-    std::string dir2(dir2_);
-    std::string filename(filename_);
+    const std::string dir1(dir1_);
+    const std::string dir2(dir2_);
+    const std::string filename(filename_);
     if (FileUtil::Exists(dir1 + DIR_SEP + filename))
         LoadFromFile(dir1 + DIR_SEP + filename, title);
     else if (FileUtil::Exists(dir2 + DIR_SEP + filename))
@@ -146,6 +146,23 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
     s256_keys[{id, field1, field2}] = key;
 }
 
+bool KeyManager::KeyFileExists(bool title) {
+    const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
+    const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
+    if (title) {
+        return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") ||
+               FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys");
+    }
+
+    if (Settings::values.use_dev_keys) {
+        return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") ||
+               FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys");
+    }
+
+    return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") ||
+           FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
+}
+
 const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
     {"master_key_00", {S128KeyType::Master, 0, 0}},
     {"master_key_01", {S128KeyType::Master, 1, 0}},
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index c09a6197e6..03152a12c9 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -102,6 +102,8 @@ public:
     void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
     void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
 
+    static bool KeyFileExists(bool title);
+
 private:
     std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index cb59c0a2bf..2deb727cca 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -156,9 +156,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
             encrypted = true;
         } else {
             if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
-                status = Loader::ResultStatus::ErrorEncrypted;
+                status = Loader::ResultStatus::ErrorMissingKeys;
             else
-                status = Loader::ResultStatus::ErrorInvalidFormat;
+                status = Loader::ResultStatus::ErrorDecrypting;
             return;
         }
     }
@@ -194,6 +194,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
             if (dec != nullptr) {
                 files.emplace_back();
                 romfs = files.back();
+            } else {
+                status = Loader::ResultStatus::ErrorMissingKeys;
+                return;
             }
         } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
@@ -211,6 +214,9 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
                     if (IsDirectoryExeFS(dirs.back()))
                         exefs = dirs.back();
                 }
+            } else {
+                status = Loader::ResultStatus::ErrorMissingKeys;
+                return;
             }
         }
     }
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index dc8d283114..cd8b41bb69 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -73,7 +73,8 @@ enum class ResultStatus {
     ErrorNotUsed,
     ErrorAlreadyLoaded,
     ErrorMemoryAllocationFailed,
-    ErrorEncrypted,
+    ErrorMissingKeys,
+    ErrorDecrypting,
     ErrorUnsupportedArch,
 };
 
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 74940fb831..d757862f0f 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -49,7 +49,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     }
 
     if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) {
-        return ResultStatus::ErrorEncrypted;
+        return ResultStatus::ErrorDecrypting;
     }
 
     auto result = nca_loader->Load(process);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index c274e5e2bd..5bdf41ea4f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -425,18 +425,49 @@ bool GMainWindow::LoadROM(const QString& filename) {
                                   tr("Could not determine the system mode."));
             break;
 
-        case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: {
+        case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: {
+            const auto reg_found = Core::Crypto::KeyManager::KeyFileExists(false);
+            const auto title_found = Core::Crypto::KeyManager::KeyFileExists(true);
+
+            std::string file_text;
+
+            if (!reg_found && !title_found) {
+                file_text = "A proper key file (prod.keys, dev.keys, or title.keys) could not be "
+                            "found. You will need to dump your keys from your switch to continue.";
+            } else if (reg_found && title_found) {
+                file_text =
+                    "Both key files were found in your config directory, but the correct key could"
+                    "not be found. You may be missing a titlekey or general key, depending on "
+                    "the game.";
+            } else if (reg_found) {
+                file_text =
+                    "The regular keys file (prod.keys/dev.keys) was found in your config, but the "
+                    "titlekeys file (title.keys) was not. You are either missing the correct "
+                    "titlekey or missing a general key required to decrypt the game.";
+            } else {
+                file_text = "The title keys file (title.keys) was found in your config, but "
+                            "the regular keys file (prod.keys/dev.keys) was not. Unfortunately, "
+                            "having the titlekey is not enough, you need additional general keys "
+                            "to properly decrypt the game. You should double-check to make sure "
+                            "your keys are correct.";
+            }
+
             QMessageBox::critical(
                 this, tr("Error while loading ROM!"),
-                tr("The game that you are trying to load must be decrypted before being used with "
-                   "yuzu. A real Switch is required.<br/><br/>"
-                   "For more information on dumping and decrypting games, please see the following "
-                   "wiki pages: <ul>"
-                   "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game "
-                   "Cartridges</a></li>"
-                   "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping "
-                   "Installed Titles</a></li>"
-                   "</ul>"));
+                tr(("The game you are trying to load is encrypted and the required keys to load "
+                    "the game could not be found in your configuration. " +
+                    file_text + " Please refer to <a href=''>How to Dump Keys</a> for help.")
+                       .c_str()));
+            break;
+        }
+        case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: {
+            QMessageBox::critical(
+                this, tr("Error while loading ROM!"),
+                tr("There was a general error while decrypting the game. This means that the keys "
+                   "necessary were found, but were either incorrect, the game itself was not a "
+                   "valid game or the game uses an unhandled cryptographic scheme. Please refer to "
+                   "<a href=''>How to Dump Keys</a> to double check that you have the correct "
+                   "keys."));
             break;
         }
         case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 97a8e13f01..5970cdb4ed 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -173,11 +173,15 @@ int main(int argc, char** argv) {
     case Core::System::ResultStatus::ErrorLoader:
         LOG_CRITICAL(Frontend, "Failed to load ROM!");
         return -1;
-    case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
-        LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
-                               "being used with yuzu. \n\n For more information on dumping and "
-                               "decrypting games, please refer to: "
-                               "https://yuzu-emu.org/wiki/dumping-game-cartridges/");
+    case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys:
+        LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required "
+                               "could not be found. Please refer to <LINK> for help");
+        return -1;
+    case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting:
+        LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a "
+                               "general error while decrypting. This could mean that the keys are "
+                               "incorrect, game is invalid or game uses an unsupported method of "
+                               "crypto. Please refer to <LINK> to double-check your keys");
         return -1;
     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
         LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");

From 0497bb5528f62f9e3db887988f0f93b4a1653a42 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 30 Jul 2018 22:04:51 -0400
Subject: [PATCH 12/13] Fix merge conflicts with opus and update docs

---
 .gitmodules             | 10 +++++-----
 src/core/CMakeLists.txt |  2 +-
 src/core/loader/xci.cpp |  2 ++
 src/yuzu/main.cpp       |  6 +++---
 src/yuzu_cmd/yuzu.cpp   |  4 ++--
 5 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/.gitmodules b/.gitmodules
index 247aac535d..4f4e8690bf 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -25,9 +25,9 @@
 [submodule "unicorn"]
     path = externals/unicorn
     url = https://github.com/yuzu-emu/unicorn
-[submodule "externals/mbedtls"]
-	path = externals/mbedtls
-	url = https://github.com/DarkLordZach/mbedtls
+[submodule "mbedtls"]
+    path = externals/mbedtls
+    url = https://github.com/DarkLordZach/mbedtls
 [submodule "opus"]
-	path = externals/opus
-	url = https://github.com/ogniK5377/opus.git
+    path = externals/opus
+    url = https://github.com/ogniK5377/opus.git
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 528d96a58b..22fad97a37 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -348,7 +348,7 @@ add_library(core STATIC
 create_target_directory_groups(core)
 
 target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn)
 
 if (ARCHITECTURE_x86_64)
     target_sources(core PRIVATE
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index d757862f0f..eb4dee2c25 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -49,6 +49,8 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     }
 
     if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) {
+        if (!Core::Crypto::KeyManager::KeyFileExists(false))
+            return ResultStatus::ErrorMissingKeys;
         return ResultStatus::ErrorDecrypting;
     }
 
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 5bdf41ea4f..34d4f0754c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -456,7 +456,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
                 this, tr("Error while loading ROM!"),
                 tr(("The game you are trying to load is encrypted and the required keys to load "
                     "the game could not be found in your configuration. " +
-                    file_text + " Please refer to <a href=''>How to Dump Keys</a> for help.")
+                    file_text + " Please refer to the yuzu wiki for help.")
                        .c_str()));
             break;
         }
@@ -465,8 +465,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
                 this, tr("Error while loading ROM!"),
                 tr("There was a general error while decrypting the game. This means that the keys "
                    "necessary were found, but were either incorrect, the game itself was not a "
-                   "valid game or the game uses an unhandled cryptographic scheme. Please refer to "
-                   "<a href=''>How to Dump Keys</a> to double check that you have the correct "
+                   "valid game or the game uses an unhandled cryptographic scheme. Please double "
+                   "check that you have the correct "
                    "keys."));
             break;
         }
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 5970cdb4ed..c3b0d9f1f6 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -175,13 +175,13 @@ int main(int argc, char** argv) {
         return -1;
     case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys:
         LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required "
-                               "could not be found. Please refer to <LINK> for help");
+                               "could not be found. Please refer to the yuzu wiki for help");
         return -1;
     case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting:
         LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a "
                                "general error while decrypting. This could mean that the keys are "
                                "incorrect, game is invalid or game uses an unsupported method of "
-                               "crypto. Please refer to <LINK> to double-check your keys");
+                               "crypto. Please double-check your keys");
         return -1;
     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
         LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");

From 13cdf1f15952cdce306e8c241b7d156496f72fe5 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 31 Jul 2018 12:27:14 -0400
Subject: [PATCH 13/13] Add missing parameter to files.push_back()

---
 src/core/file_sys/content_archive.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 2deb727cca..79e70f6ef1 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -188,11 +188,11 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
                 section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
             const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
-            const auto dec =
+            auto dec =
                 Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
                         romfs_offset);
             if (dec != nullptr) {
-                files.emplace_back();
+                files.push_back(std::move(dec));
                 romfs = files.back();
             } else {
                 status = Loader::ResultStatus::ErrorMissingKeys;
@@ -204,13 +204,13 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
                          section.pfs0.pfs0_header_offset;
             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
                                                   header.section_tables[i].media_offset);
-            const auto dec =
+            auto dec =
                 Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
             if (dec != nullptr) {
-                auto npfs = std::make_shared<PartitionFilesystem>(dec);
+                auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
 
                 if (npfs->GetStatus() == Loader::ResultStatus::Success) {
-                    dirs.emplace_back(npfs);
+                    dirs.push_back(std::move(npfs));
                     if (IsDirectoryExeFS(dirs.back()))
                         exefs = dirs.back();
                 }