diff options
39 files changed, 1404 insertions, 80 deletions
diff --git a/.gitmodules b/.gitmodules index 0f94613c4..4f4e8690b 100644 --- a/.gitmodules +++ b/.gitmodules | |||
| @@ -25,6 +25,9 @@ | |||
| 25 | [submodule "unicorn"] | 25 | [submodule "unicorn"] |
| 26 | path = externals/unicorn | 26 | path = externals/unicorn |
| 27 | url = https://github.com/yuzu-emu/unicorn | 27 | url = https://github.com/yuzu-emu/unicorn |
| 28 | [submodule "mbedtls"] | ||
| 29 | path = externals/mbedtls | ||
| 30 | url = https://github.com/DarkLordZach/mbedtls | ||
| 28 | [submodule "opus"] | 31 | [submodule "opus"] |
| 29 | path = externals/opus | 32 | path = externals/opus |
| 30 | url = https://github.com/ogniK5377/opus.git | 33 | url = https://github.com/ogniK5377/opus.git |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4636761e5..987d0142a 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -35,6 +35,10 @@ set(LZ4_BUNDLED_MODE ON) | |||
| 35 | add_subdirectory(lz4/contrib/cmake_unofficial) | 35 | add_subdirectory(lz4/contrib/cmake_unofficial) |
| 36 | target_include_directories(lz4_static INTERFACE ./lz4/lib) | 36 | target_include_directories(lz4_static INTERFACE ./lz4/lib) |
| 37 | 37 | ||
| 38 | # mbedtls | ||
| 39 | add_subdirectory(mbedtls) | ||
| 40 | target_include_directories(mbedtls PUBLIC ./mbedtls/include) | ||
| 41 | |||
| 38 | # MicroProfile | 42 | # MicroProfile |
| 39 | add_library(microprofile INTERFACE) | 43 | add_library(microprofile INTERFACE) |
| 40 | target_include_directories(microprofile INTERFACE ./microprofile) | 44 | target_include_directories(microprofile INTERFACE ./microprofile) |
diff --git a/externals/mbedtls b/externals/mbedtls new file mode 160000 | |||
| Subproject 06442b840ea62e2ceda0dc58f255cfff49fed5b | |||
diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 6799a357a..df2ce80b1 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #define SDMC_DIR "sdmc" | 32 | #define SDMC_DIR "sdmc" |
| 33 | #define NAND_DIR "nand" | 33 | #define NAND_DIR "nand" |
| 34 | #define SYSDATA_DIR "sysdata" | 34 | #define SYSDATA_DIR "sysdata" |
| 35 | #define KEYS_DIR "keys" | ||
| 35 | #define LOG_DIR "log" | 36 | #define LOG_DIR "log" |
| 36 | 37 | ||
| 37 | // Filenames | 38 | // Filenames |
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b8dd92b65..7aeda737f 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) { | |||
| 706 | paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); | 706 | paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); |
| 707 | paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); | 707 | paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); |
| 708 | paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); | 708 | paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); |
| 709 | paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); | ||
| 709 | // TODO: Put the logs in a better location for each OS | 710 | // TODO: Put the logs in a better location for each OS |
| 710 | paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); | 711 | paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); |
| 711 | } | 712 | } |
| @@ -736,6 +737,19 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { | |||
| 736 | return paths[path]; | 737 | return paths[path]; |
| 737 | } | 738 | } |
| 738 | 739 | ||
| 740 | std::string GetHactoolConfigurationPath() { | ||
| 741 | #ifdef _WIN32 | ||
| 742 | PWSTR pw_local_path = nullptr; | ||
| 743 | if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK) | ||
| 744 | return ""; | ||
| 745 | std::string local_path = Common::UTF16ToUTF8(pw_local_path); | ||
| 746 | CoTaskMemFree(pw_local_path); | ||
| 747 | return local_path + "\\.switch"; | ||
| 748 | #else | ||
| 749 | return GetHomeDirectory() + "/.switch"; | ||
| 750 | #endif | ||
| 751 | } | ||
| 752 | |||
| 739 | size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { | 753 | size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { |
| 740 | return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); | 754 | return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); |
| 741 | } | 755 | } |
diff --git a/src/common/file_util.h b/src/common/file_util.h index bc9272d89..28697d527 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h | |||
| @@ -23,6 +23,7 @@ namespace FileUtil { | |||
| 23 | enum class UserPath { | 23 | enum class UserPath { |
| 24 | CacheDir, | 24 | CacheDir, |
| 25 | ConfigDir, | 25 | ConfigDir, |
| 26 | KeysDir, | ||
| 26 | LogDir, | 27 | LogDir, |
| 27 | NANDDir, | 28 | NANDDir, |
| 28 | RootDir, | 29 | RootDir, |
| @@ -125,6 +126,8 @@ bool SetCurrentDir(const std::string& directory); | |||
| 125 | // directory. To be used in "multi-user" mode (that is, installed). | 126 | // directory. To be used in "multi-user" mode (that is, installed). |
| 126 | const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); | 127 | const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); |
| 127 | 128 | ||
| 129 | std::string GetHactoolConfigurationPath(); | ||
| 130 | |||
| 128 | // Returns the path to where the sys file are | 131 | // Returns the path to where the sys file are |
| 129 | std::string GetSysDirectory(); | 132 | std::string GetSysDirectory(); |
| 130 | 133 | ||
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index c6a21d993..34dec06fe 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
| @@ -217,6 +217,7 @@ void FileBackend::Write(const Entry& entry) { | |||
| 217 | CLS(Input) \ | 217 | CLS(Input) \ |
| 218 | CLS(Network) \ | 218 | CLS(Network) \ |
| 219 | CLS(Loader) \ | 219 | CLS(Loader) \ |
| 220 | CLS(Crypto) \ | ||
| 220 | CLS(WebService) | 221 | CLS(WebService) |
| 221 | 222 | ||
| 222 | // GetClassName is a macro defined by Windows.h, grrr... | 223 | // GetClassName is a macro defined by Windows.h, grrr... |
diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 3d6a161a5..dd5c9e6be 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h | |||
| @@ -102,6 +102,7 @@ enum class Class : ClassType { | |||
| 102 | Audio_DSP, ///< The HLE implementation of the DSP | 102 | Audio_DSP, ///< The HLE implementation of the DSP |
| 103 | Audio_Sink, ///< Emulator audio output backend | 103 | Audio_Sink, ///< Emulator audio output backend |
| 104 | Loader, ///< ROM loader | 104 | Loader, ///< ROM loader |
| 105 | Crypto, ///< Cryptographic engine/functions | ||
| 105 | Input, ///< Input emulation | 106 | Input, ///< Input emulation |
| 106 | Network, ///< Network emulation | 107 | Network, ///< Network emulation |
| 107 | WebService, ///< Interface to yuzu Web Services | 108 | WebService, ///< Interface to yuzu Web Services |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 884c28e20..28de22398 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -12,6 +12,16 @@ add_library(core STATIC | |||
| 12 | core_timing.h | 12 | core_timing.h |
| 13 | core_timing_util.cpp | 13 | core_timing_util.cpp |
| 14 | core_timing_util.h | 14 | core_timing_util.h |
| 15 | crypto/aes_util.cpp | ||
| 16 | crypto/aes_util.h | ||
| 17 | crypto/encryption_layer.cpp | ||
| 18 | crypto/encryption_layer.h | ||
| 19 | crypto/key_manager.cpp | ||
| 20 | crypto/key_manager.h | ||
| 21 | crypto/ctr_encryption_layer.cpp | ||
| 22 | crypto/ctr_encryption_layer.h | ||
| 23 | file_sys/card_image.cpp | ||
| 24 | file_sys/card_image.h | ||
| 15 | file_sys/content_archive.cpp | 25 | file_sys/content_archive.cpp |
| 16 | file_sys/content_archive.h | 26 | file_sys/content_archive.h |
| 17 | file_sys/control_metadata.cpp | 27 | file_sys/control_metadata.cpp |
| @@ -327,6 +337,8 @@ add_library(core STATIC | |||
| 327 | loader/nro.h | 337 | loader/nro.h |
| 328 | loader/nso.cpp | 338 | loader/nso.cpp |
| 329 | loader/nso.h | 339 | loader/nso.h |
| 340 | loader/xci.cpp | ||
| 341 | loader/xci.h | ||
| 330 | memory.cpp | 342 | memory.cpp |
| 331 | memory.h | 343 | memory.h |
| 332 | memory_hook.cpp | 344 | memory_hook.cpp |
| @@ -346,7 +358,7 @@ add_library(core STATIC | |||
| 346 | create_target_directory_groups(core) | 358 | create_target_directory_groups(core) |
| 347 | 359 | ||
| 348 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | 360 | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) |
| 349 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn) | 361 | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn) |
| 350 | 362 | ||
| 351 | if (ARCHITECTURE_x86_64) | 363 | if (ARCHITECTURE_x86_64) |
| 352 | target_sources(core PRIVATE | 364 | target_sources(core PRIVATE |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 9824769cf..e51e66550 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -99,8 +99,10 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||
| 99 | static_cast<int>(system_mode.second)); | 99 | static_cast<int>(system_mode.second)); |
| 100 | 100 | ||
| 101 | switch (system_mode.second) { | 101 | switch (system_mode.second) { |
| 102 | case Loader::ResultStatus::ErrorEncrypted: | 102 | case Loader::ResultStatus::ErrorMissingKeys: |
| 103 | return ResultStatus::ErrorLoader_ErrorEncrypted; | 103 | return ResultStatus::ErrorLoader_ErrorMissingKeys; |
| 104 | case Loader::ResultStatus::ErrorDecrypting: | ||
| 105 | return ResultStatus::ErrorLoader_ErrorDecrypting; | ||
| 104 | case Loader::ResultStatus::ErrorInvalidFormat: | 106 | case Loader::ResultStatus::ErrorInvalidFormat: |
| 105 | return ResultStatus::ErrorLoader_ErrorInvalidFormat; | 107 | return ResultStatus::ErrorLoader_ErrorInvalidFormat; |
| 106 | case Loader::ResultStatus::ErrorUnsupportedArch: | 108 | case Loader::ResultStatus::ErrorUnsupportedArch: |
| @@ -124,8 +126,10 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||
| 124 | System::Shutdown(); | 126 | System::Shutdown(); |
| 125 | 127 | ||
| 126 | switch (load_result) { | 128 | switch (load_result) { |
| 127 | case Loader::ResultStatus::ErrorEncrypted: | 129 | case Loader::ResultStatus::ErrorMissingKeys: |
| 128 | return ResultStatus::ErrorLoader_ErrorEncrypted; | 130 | return ResultStatus::ErrorLoader_ErrorMissingKeys; |
| 131 | case Loader::ResultStatus::ErrorDecrypting: | ||
| 132 | return ResultStatus::ErrorLoader_ErrorDecrypting; | ||
| 129 | case Loader::ResultStatus::ErrorInvalidFormat: | 133 | case Loader::ResultStatus::ErrorInvalidFormat: |
| 130 | return ResultStatus::ErrorLoader_ErrorInvalidFormat; | 134 | return ResultStatus::ErrorLoader_ErrorInvalidFormat; |
| 131 | case Loader::ResultStatus::ErrorUnsupportedArch: | 135 | case Loader::ResultStatus::ErrorUnsupportedArch: |
diff --git a/src/core/core.h b/src/core/core.h index ed475ac4e..4c9967559 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -43,12 +43,14 @@ public: | |||
| 43 | 43 | ||
| 44 | /// Enumeration representing the return values of the System Initialize and Load process. | 44 | /// Enumeration representing the return values of the System Initialize and Load process. |
| 45 | enum class ResultStatus : u32 { | 45 | enum class ResultStatus : u32 { |
| 46 | Success, ///< Succeeded | 46 | Success, ///< Succeeded |
| 47 | ErrorNotInitialized, ///< Error trying to use core prior to initialization | 47 | ErrorNotInitialized, ///< Error trying to use core prior to initialization |
| 48 | ErrorGetLoader, ///< Error finding the correct application loader | 48 | ErrorGetLoader, ///< Error finding the correct application loader |
| 49 | ErrorSystemMode, ///< Error determining the system mode | 49 | ErrorSystemMode, ///< Error determining the system mode |
| 50 | ErrorLoader, ///< Error loading the specified application | 50 | ErrorLoader, ///< Error loading the specified application |
| 51 | ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption | 51 | ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be |
| 52 | ///< found. | ||
| 53 | ErrorLoader_ErrorDecrypting, ///< Error loading the specified application due to encryption | ||
| 52 | ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an | 54 | ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an |
| 53 | /// invalid format | 55 | /// invalid format |
| 54 | ErrorSystemFiles, ///< Error in finding system files | 56 | ErrorSystemFiles, ///< Error in finding system files |
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp new file mode 100644 index 000000000..4690af5f8 --- /dev/null +++ b/src/core/crypto/aes_util.cpp | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <mbedtls/cipher.h> | ||
| 6 | #include "core/crypto/aes_util.h" | ||
| 7 | #include "core/crypto/key_manager.h" | ||
| 8 | |||
| 9 | namespace Core::Crypto { | ||
| 10 | |||
| 11 | static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR), | ||
| 12 | "CTR has incorrect value."); | ||
| 13 | static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB), | ||
| 14 | "ECB has incorrect value."); | ||
| 15 | static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS), | ||
| 16 | "XTS has incorrect value."); | ||
| 17 | |||
| 18 | // Structure to hide mbedtls types from header file | ||
| 19 | struct CipherContext { | ||
| 20 | mbedtls_cipher_context_t encryption_context; | ||
| 21 | mbedtls_cipher_context_t decryption_context; | ||
| 22 | }; | ||
| 23 | |||
| 24 | template <typename Key, size_t KeySize> | ||
| 25 | Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) | ||
| 26 | : ctx(std::make_unique<CipherContext>()) { | ||
| 27 | mbedtls_cipher_init(&ctx->encryption_context); | ||
| 28 | mbedtls_cipher_init(&ctx->decryption_context); | ||
| 29 | |||
| 30 | ASSERT_MSG((mbedtls_cipher_setup( | ||
| 31 | &ctx->encryption_context, | ||
| 32 | mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) || | ||
| 33 | mbedtls_cipher_setup( | ||
| 34 | &ctx->decryption_context, | ||
| 35 | mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0, | ||
| 36 | "Failed to initialize mbedtls ciphers."); | ||
| 37 | |||
| 38 | ASSERT( | ||
| 39 | !mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); | ||
| 40 | ASSERT( | ||
| 41 | !mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); | ||
| 42 | //"Failed to set key on mbedtls ciphers."); | ||
| 43 | } | ||
| 44 | |||
| 45 | template <typename Key, size_t KeySize> | ||
| 46 | AESCipher<Key, KeySize>::~AESCipher() { | ||
| 47 | mbedtls_cipher_free(&ctx->encryption_context); | ||
| 48 | mbedtls_cipher_free(&ctx->decryption_context); | ||
| 49 | } | ||
| 50 | |||
| 51 | template <typename Key, size_t KeySize> | ||
| 52 | void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) { | ||
| 53 | ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) || | ||
| 54 | mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0, | ||
| 55 | "Failed to set IV on mbedtls ciphers."); | ||
| 56 | } | ||
| 57 | |||
| 58 | template <typename Key, size_t KeySize> | ||
| 59 | void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) { | ||
| 60 | size_t written = 0; | ||
| 61 | |||
| 62 | const auto context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context; | ||
| 63 | |||
| 64 | mbedtls_cipher_reset(context); | ||
| 65 | |||
| 66 | if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { | ||
| 67 | mbedtls_cipher_update(context, src, size, dest, &written); | ||
| 68 | if (written != size) | ||
| 69 | LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", | ||
| 70 | size, written); | ||
| 71 | } else { | ||
| 72 | const auto block_size = mbedtls_cipher_get_block_size(context); | ||
| 73 | |||
| 74 | for (size_t offset = 0; offset < size; offset += block_size) { | ||
| 75 | auto length = std::min<size_t>(block_size, size - offset); | ||
| 76 | mbedtls_cipher_update(context, src + offset, length, dest + offset, &written); | ||
| 77 | if (written != length) | ||
| 78 | LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", | ||
| 79 | length, written); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | mbedtls_cipher_finish(context, nullptr, nullptr); | ||
| 84 | } | ||
| 85 | |||
| 86 | template <typename Key, size_t KeySize> | ||
| 87 | void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, | ||
| 88 | size_t sector_size, Op op) { | ||
| 89 | if (size % sector_size > 0) { | ||
| 90 | LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size."); | ||
| 91 | return; | ||
| 92 | } | ||
| 93 | |||
| 94 | for (size_t i = 0; i < size; i += sector_size) { | ||
| 95 | SetIV(CalculateNintendoTweak(sector_id++)); | ||
| 96 | Transcode<u8, u8>(src + i, sector_size, dest + i, op); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | template <typename Key, size_t KeySize> | ||
| 101 | std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id) { | ||
| 102 | std::vector<u8> out(0x10); | ||
| 103 | for (size_t i = 0xF; i <= 0xF; --i) { | ||
| 104 | out[i] = sector_id & 0xFF; | ||
| 105 | sector_id >>= 8; | ||
| 106 | } | ||
| 107 | return out; | ||
| 108 | } | ||
| 109 | |||
| 110 | template class AESCipher<Key128>; | ||
| 111 | template class AESCipher<Key256>; | ||
| 112 | } // 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 new file mode 100644 index 000000000..5b0b02738 --- /dev/null +++ b/src/core/crypto/aes_util.h | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include <type_traits> | ||
| 9 | #include <vector> | ||
| 10 | #include "common/assert.h" | ||
| 11 | #include "core/file_sys/vfs.h" | ||
| 12 | |||
| 13 | namespace Core::Crypto { | ||
| 14 | |||
| 15 | struct CipherContext; | ||
| 16 | |||
| 17 | enum class Mode { | ||
| 18 | CTR = 11, | ||
| 19 | ECB = 2, | ||
| 20 | XTS = 70, | ||
| 21 | }; | ||
| 22 | |||
| 23 | enum class Op { | ||
| 24 | Encrypt, | ||
| 25 | Decrypt, | ||
| 26 | }; | ||
| 27 | |||
| 28 | template <typename Key, size_t KeySize = sizeof(Key)> | ||
| 29 | class AESCipher { | ||
| 30 | static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8."); | ||
| 31 | static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); | ||
| 32 | |||
| 33 | public: | ||
| 34 | AESCipher(Key key, Mode mode); | ||
| 35 | |||
| 36 | ~AESCipher(); | ||
| 37 | |||
| 38 | void SetIV(std::vector<u8> iv); | ||
| 39 | |||
| 40 | template <typename Source, typename Dest> | ||
| 41 | void Transcode(const Source* src, size_t size, Dest* dest, Op op) { | ||
| 42 | Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op); | ||
| 43 | } | ||
| 44 | |||
| 45 | void Transcode(const u8* src, size_t size, u8* dest, Op op); | ||
| 46 | |||
| 47 | template <typename Source, typename Dest> | ||
| 48 | void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id, | ||
| 49 | size_t sector_size, Op op) { | ||
| 50 | XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id, | ||
| 51 | sector_size, op); | ||
| 52 | } | ||
| 53 | |||
| 54 | void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, | ||
| 55 | Op op); | ||
| 56 | |||
| 57 | private: | ||
| 58 | std::unique_ptr<CipherContext> ctx; | ||
| 59 | |||
| 60 | static std::vector<u8> CalculateNintendoTweak(size_t sector_id); | ||
| 61 | }; | ||
| 62 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp new file mode 100644 index 000000000..106db02b3 --- /dev/null +++ b/src/core/crypto/ctr_encryption_layer.cpp | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cstring> | ||
| 6 | #include "common/assert.h" | ||
| 7 | #include "core/crypto/ctr_encryption_layer.h" | ||
| 8 | |||
| 9 | namespace Core::Crypto { | ||
| 10 | |||
| 11 | CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) | ||
| 12 | : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), | ||
| 13 | iv(16, 0) {} | ||
| 14 | |||
| 15 | size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { | ||
| 16 | if (length == 0) | ||
| 17 | return 0; | ||
| 18 | |||
| 19 | const auto sector_offset = offset & 0xF; | ||
| 20 | if (sector_offset == 0) { | ||
| 21 | UpdateIV(base_offset + offset); | ||
| 22 | std::vector<u8> raw = base->ReadBytes(length, offset); | ||
| 23 | if (raw.size() != length) | ||
| 24 | return Read(data, raw.size(), offset); | ||
| 25 | cipher.Transcode(raw.data(), length, data, Op::Decrypt); | ||
| 26 | return length; | ||
| 27 | } | ||
| 28 | |||
| 29 | // offset does not fall on block boundary (0x10) | ||
| 30 | std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset); | ||
| 31 | UpdateIV(base_offset + offset - sector_offset); | ||
| 32 | cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt); | ||
| 33 | size_t read = 0x10 - sector_offset; | ||
| 34 | |||
| 35 | if (length + sector_offset < 0x10) { | ||
| 36 | std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); | ||
| 37 | return read; | ||
| 38 | } | ||
| 39 | std::memcpy(data, block.data() + sector_offset, read); | ||
| 40 | return read + Read(data + read, length - read, offset + read); | ||
| 41 | } | ||
| 42 | |||
| 43 | void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) { | ||
| 44 | const auto length = std::min(iv_.size(), iv.size()); | ||
| 45 | iv.assign(iv_.cbegin(), iv_.cbegin() + length); | ||
| 46 | } | ||
| 47 | |||
| 48 | void CTREncryptionLayer::UpdateIV(size_t offset) const { | ||
| 49 | offset >>= 4; | ||
| 50 | for (size_t i = 0; i < 8; ++i) { | ||
| 51 | iv[16 - i - 1] = offset & 0xFF; | ||
| 52 | offset >>= 8; | ||
| 53 | } | ||
| 54 | cipher.SetIV(iv); | ||
| 55 | } | ||
| 56 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h new file mode 100644 index 000000000..11b8683c7 --- /dev/null +++ b/src/core/crypto/ctr_encryption_layer.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <vector> | ||
| 8 | #include "core/crypto/aes_util.h" | ||
| 9 | #include "core/crypto/encryption_layer.h" | ||
| 10 | #include "core/crypto/key_manager.h" | ||
| 11 | |||
| 12 | namespace Core::Crypto { | ||
| 13 | |||
| 14 | // Sits on top of a VirtualFile and provides CTR-mode AES decription. | ||
| 15 | class CTREncryptionLayer : public EncryptionLayer { | ||
| 16 | public: | ||
| 17 | CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); | ||
| 18 | |||
| 19 | size_t Read(u8* data, size_t length, size_t offset) const override; | ||
| 20 | |||
| 21 | void SetIV(const std::vector<u8>& iv); | ||
| 22 | |||
| 23 | private: | ||
| 24 | size_t base_offset; | ||
| 25 | |||
| 26 | // Must be mutable as operations modify cipher contexts. | ||
| 27 | mutable AESCipher<Key128> cipher; | ||
| 28 | mutable std::vector<u8> iv; | ||
| 29 | |||
| 30 | void UpdateIV(size_t offset) const; | ||
| 31 | }; | ||
| 32 | |||
| 33 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp new file mode 100644 index 000000000..4204527e3 --- /dev/null +++ b/src/core/crypto/encryption_layer.cpp | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "core/crypto/encryption_layer.h" | ||
| 6 | |||
| 7 | namespace Core::Crypto { | ||
| 8 | |||
| 9 | EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {} | ||
| 10 | |||
| 11 | std::string EncryptionLayer::GetName() const { | ||
| 12 | return base->GetName(); | ||
| 13 | } | ||
| 14 | |||
| 15 | size_t EncryptionLayer::GetSize() const { | ||
| 16 | return base->GetSize(); | ||
| 17 | } | ||
| 18 | |||
| 19 | bool EncryptionLayer::Resize(size_t new_size) { | ||
| 20 | return false; | ||
| 21 | } | ||
| 22 | |||
| 23 | std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const { | ||
| 24 | return base->GetContainingDirectory(); | ||
| 25 | } | ||
| 26 | |||
| 27 | bool EncryptionLayer::IsWritable() const { | ||
| 28 | return false; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool EncryptionLayer::IsReadable() const { | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | |||
| 35 | size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { | ||
| 36 | return 0; | ||
| 37 | } | ||
| 38 | |||
| 39 | bool EncryptionLayer::Rename(std::string_view name) { | ||
| 40 | return base->Rename(name); | ||
| 41 | } | ||
| 42 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h new file mode 100644 index 000000000..71bca1f23 --- /dev/null +++ b/src/core/crypto/encryption_layer.h | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "core/file_sys/vfs.h" | ||
| 8 | |||
| 9 | namespace Core::Crypto { | ||
| 10 | |||
| 11 | // Basically non-functional class that implements all of the methods that are irrelevant to an | ||
| 12 | // EncryptionLayer. Reduces duplicate code. | ||
| 13 | class EncryptionLayer : public FileSys::VfsFile { | ||
| 14 | public: | ||
| 15 | explicit EncryptionLayer(FileSys::VirtualFile base); | ||
| 16 | |||
| 17 | size_t Read(u8* data, size_t length, size_t offset) const override = 0; | ||
| 18 | |||
| 19 | std::string GetName() const override; | ||
| 20 | size_t GetSize() const override; | ||
| 21 | bool Resize(size_t new_size) override; | ||
| 22 | std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override; | ||
| 23 | bool IsWritable() const override; | ||
| 24 | bool IsReadable() const override; | ||
| 25 | size_t Write(const u8* data, size_t length, size_t offset) override; | ||
| 26 | bool Rename(std::string_view name) override; | ||
| 27 | |||
| 28 | protected: | ||
| 29 | FileSys::VirtualFile base; | ||
| 30 | }; | ||
| 31 | |||
| 32 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp new file mode 100644 index 000000000..678ac5752 --- /dev/null +++ b/src/core/crypto/key_manager.cpp | |||
| @@ -0,0 +1,215 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <fstream> | ||
| 7 | #include <locale> | ||
| 8 | #include <sstream> | ||
| 9 | #include <string_view> | ||
| 10 | #include <mbedtls/sha256.h> | ||
| 11 | #include "common/assert.h" | ||
| 12 | #include "common/common_paths.h" | ||
| 13 | #include "common/file_util.h" | ||
| 14 | #include "common/logging/log.h" | ||
| 15 | #include "core/crypto/key_manager.h" | ||
| 16 | #include "core/settings.h" | ||
| 17 | #include "key_manager.h" | ||
| 18 | |||
| 19 | namespace Core::Crypto { | ||
| 20 | |||
| 21 | static u8 ToHexNibble(char c1) { | ||
| 22 | if (c1 >= 65 && c1 <= 70) | ||
| 23 | return c1 - 55; | ||
| 24 | if (c1 >= 97 && c1 <= 102) | ||
| 25 | return c1 - 87; | ||
| 26 | if (c1 >= 48 && c1 <= 57) | ||
| 27 | return c1 - 48; | ||
| 28 | throw std::logic_error("Invalid hex digit"); | ||
| 29 | } | ||
| 30 | |||
| 31 | template <size_t Size> | ||
| 32 | static std::array<u8, Size> HexStringToArray(std::string_view str) { | ||
| 33 | std::array<u8, Size> out{}; | ||
| 34 | for (size_t i = 0; i < 2 * Size; i += 2) { | ||
| 35 | auto d1 = str[i]; | ||
| 36 | auto d2 = str[i + 1]; | ||
| 37 | out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); | ||
| 38 | } | ||
| 39 | return out; | ||
| 40 | } | ||
| 41 | |||
| 42 | std::array<u8, 16> operator""_array16(const char* str, size_t len) { | ||
| 43 | if (len != 32) | ||
| 44 | throw std::logic_error("Not of correct size."); | ||
| 45 | return HexStringToArray<16>(str); | ||
| 46 | } | ||
| 47 | |||
| 48 | std::array<u8, 32> operator""_array32(const char* str, size_t len) { | ||
| 49 | if (len != 64) | ||
| 50 | throw std::logic_error("Not of correct size."); | ||
| 51 | return HexStringToArray<32>(str); | ||
| 52 | } | ||
| 53 | |||
| 54 | KeyManager::KeyManager() { | ||
| 55 | // Initialize keys | ||
| 56 | const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); | ||
| 57 | const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); | ||
| 58 | if (Settings::values.use_dev_keys) { | ||
| 59 | dev_mode = true; | ||
| 60 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); | ||
| 61 | } else { | ||
| 62 | dev_mode = false; | ||
| 63 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); | ||
| 64 | } | ||
| 65 | |||
| 66 | AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); | ||
| 67 | } | ||
| 68 | |||
| 69 | void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { | ||
| 70 | const auto filename = std::string(filename_); | ||
| 71 | std::ifstream file(filename); | ||
| 72 | if (!file.is_open()) | ||
| 73 | return; | ||
| 74 | |||
| 75 | std::string line; | ||
| 76 | while (std::getline(file, line)) { | ||
| 77 | std::vector<std::string> out; | ||
| 78 | std::stringstream stream(line); | ||
| 79 | std::string item; | ||
| 80 | while (std::getline(stream, item, '=')) | ||
| 81 | out.push_back(std::move(item)); | ||
| 82 | |||
| 83 | if (out.size() != 2) | ||
| 84 | continue; | ||
| 85 | |||
| 86 | out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); | ||
| 87 | out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); | ||
| 88 | |||
| 89 | if (is_title_keys) { | ||
| 90 | auto rights_id_raw = HexStringToArray<16>(out[0]); | ||
| 91 | u128 rights_id{}; | ||
| 92 | std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); | ||
| 93 | Key128 key = HexStringToArray<16>(out[1]); | ||
| 94 | SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||
| 95 | } else { | ||
| 96 | std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); | ||
| 97 | if (s128_file_id.find(out[0]) != s128_file_id.end()) { | ||
| 98 | const auto index = s128_file_id.at(out[0]); | ||
| 99 | Key128 key = HexStringToArray<16>(out[1]); | ||
| 100 | SetKey(index.type, key, index.field1, index.field2); | ||
| 101 | } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { | ||
| 102 | const auto index = s256_file_id.at(out[0]); | ||
| 103 | Key256 key = HexStringToArray<32>(out[1]); | ||
| 104 | SetKey(index.type, key, index.field1, index.field2); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_, | ||
| 111 | std::string_view filename_, bool title) { | ||
| 112 | const std::string dir1(dir1_); | ||
| 113 | const std::string dir2(dir2_); | ||
| 114 | const std::string filename(filename_); | ||
| 115 | if (FileUtil::Exists(dir1 + DIR_SEP + filename)) | ||
| 116 | LoadFromFile(dir1 + DIR_SEP + filename, title); | ||
| 117 | else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) | ||
| 118 | LoadFromFile(dir2 + DIR_SEP + filename, title); | ||
| 119 | } | ||
| 120 | |||
| 121 | bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const { | ||
| 122 | return s128_keys.find({id, field1, field2}) != s128_keys.end(); | ||
| 123 | } | ||
| 124 | |||
| 125 | bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { | ||
| 126 | return s256_keys.find({id, field1, field2}) != s256_keys.end(); | ||
| 127 | } | ||
| 128 | |||
| 129 | Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { | ||
| 130 | if (!HasKey(id, field1, field2)) | ||
| 131 | return {}; | ||
| 132 | return s128_keys.at({id, field1, field2}); | ||
| 133 | } | ||
| 134 | |||
| 135 | Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { | ||
| 136 | if (!HasKey(id, field1, field2)) | ||
| 137 | return {}; | ||
| 138 | return s256_keys.at({id, field1, field2}); | ||
| 139 | } | ||
| 140 | |||
| 141 | void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { | ||
| 142 | s128_keys[{id, field1, field2}] = key; | ||
| 143 | } | ||
| 144 | |||
| 145 | void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { | ||
| 146 | s256_keys[{id, field1, field2}] = key; | ||
| 147 | } | ||
| 148 | |||
| 149 | bool KeyManager::KeyFileExists(bool title) { | ||
| 150 | const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); | ||
| 151 | const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); | ||
| 152 | if (title) { | ||
| 153 | return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || | ||
| 154 | FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); | ||
| 155 | } | ||
| 156 | |||
| 157 | if (Settings::values.use_dev_keys) { | ||
| 158 | return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || | ||
| 159 | FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); | ||
| 160 | } | ||
| 161 | |||
| 162 | return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || | ||
| 163 | FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); | ||
| 164 | } | ||
| 165 | |||
| 166 | const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { | ||
| 167 | {"master_key_00", {S128KeyType::Master, 0, 0}}, | ||
| 168 | {"master_key_01", {S128KeyType::Master, 1, 0}}, | ||
| 169 | {"master_key_02", {S128KeyType::Master, 2, 0}}, | ||
| 170 | {"master_key_03", {S128KeyType::Master, 3, 0}}, | ||
| 171 | {"master_key_04", {S128KeyType::Master, 4, 0}}, | ||
| 172 | {"package1_key_00", {S128KeyType::Package1, 0, 0}}, | ||
| 173 | {"package1_key_01", {S128KeyType::Package1, 1, 0}}, | ||
| 174 | {"package1_key_02", {S128KeyType::Package1, 2, 0}}, | ||
| 175 | {"package1_key_03", {S128KeyType::Package1, 3, 0}}, | ||
| 176 | {"package1_key_04", {S128KeyType::Package1, 4, 0}}, | ||
| 177 | {"package2_key_00", {S128KeyType::Package2, 0, 0}}, | ||
| 178 | {"package2_key_01", {S128KeyType::Package2, 1, 0}}, | ||
| 179 | {"package2_key_02", {S128KeyType::Package2, 2, 0}}, | ||
| 180 | {"package2_key_03", {S128KeyType::Package2, 3, 0}}, | ||
| 181 | {"package2_key_04", {S128KeyType::Package2, 4, 0}}, | ||
| 182 | {"titlekek_00", {S128KeyType::Titlekek, 0, 0}}, | ||
| 183 | {"titlekek_01", {S128KeyType::Titlekek, 1, 0}}, | ||
| 184 | {"titlekek_02", {S128KeyType::Titlekek, 2, 0}}, | ||
| 185 | {"titlekek_03", {S128KeyType::Titlekek, 3, 0}}, | ||
| 186 | {"titlekek_04", {S128KeyType::Titlekek, 4, 0}}, | ||
| 187 | {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, | ||
| 188 | {"key_area_key_application_00", | ||
| 189 | {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||
| 190 | {"key_area_key_application_01", | ||
| 191 | {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||
| 192 | {"key_area_key_application_02", | ||
| 193 | {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||
| 194 | {"key_area_key_application_03", | ||
| 195 | {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||
| 196 | {"key_area_key_application_04", | ||
| 197 | {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||
| 198 | {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||
| 199 | {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||
| 200 | {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||
| 201 | {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||
| 202 | {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||
| 203 | {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}}, | ||
| 204 | {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}}, | ||
| 205 | {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, | ||
| 206 | {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, | ||
| 207 | {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, | ||
| 208 | }; | ||
| 209 | |||
| 210 | const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { | ||
| 211 | {"header_key", {S256KeyType::Header, 0, 0}}, | ||
| 212 | {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, | ||
| 213 | {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, | ||
| 214 | }; | ||
| 215 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h new file mode 100644 index 000000000..03152a12c --- /dev/null +++ b/src/core/crypto/key_manager.h | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <type_traits> | ||
| 9 | #include <unordered_map> | ||
| 10 | #include <vector> | ||
| 11 | #include <fmt/format.h> | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace Core::Crypto { | ||
| 15 | |||
| 16 | using Key128 = std::array<u8, 0x10>; | ||
| 17 | using Key256 = std::array<u8, 0x20>; | ||
| 18 | using SHA256Hash = std::array<u8, 0x20>; | ||
| 19 | |||
| 20 | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | ||
| 21 | static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); | ||
| 22 | |||
| 23 | enum class S256KeyType : u64 { | ||
| 24 | Header, // | ||
| 25 | SDSave, // | ||
| 26 | SDNCA, // | ||
| 27 | }; | ||
| 28 | |||
| 29 | enum class S128KeyType : u64 { | ||
| 30 | Master, // f1=crypto revision | ||
| 31 | Package1, // f1=crypto revision | ||
| 32 | Package2, // f1=crypto revision | ||
| 33 | Titlekek, // f1=crypto revision | ||
| 34 | ETicketRSAKek, // | ||
| 35 | KeyArea, // f1=crypto revision f2=type {app, ocean, system} | ||
| 36 | SDSeed, // | ||
| 37 | Titlekey, // f1=rights id LSB f2=rights id MSB | ||
| 38 | }; | ||
| 39 | |||
| 40 | enum class KeyAreaKeyType : u8 { | ||
| 41 | Application, | ||
| 42 | Ocean, | ||
| 43 | System, | ||
| 44 | }; | ||
| 45 | |||
| 46 | template <typename KeyType> | ||
| 47 | struct KeyIndex { | ||
| 48 | KeyType type; | ||
| 49 | u64 field1; | ||
| 50 | u64 field2; | ||
| 51 | |||
| 52 | std::string DebugInfo() const { | ||
| 53 | u8 key_size = 16; | ||
| 54 | if constexpr (std::is_same_v<KeyType, S256KeyType>) | ||
| 55 | key_size = 32; | ||
| 56 | return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size, | ||
| 57 | static_cast<u8>(type), field1, field2); | ||
| 58 | } | ||
| 59 | }; | ||
| 60 | |||
| 61 | // The following two (== and hash) are so KeyIndex can be a key in unordered_map | ||
| 62 | |||
| 63 | template <typename KeyType> | ||
| 64 | bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { | ||
| 65 | return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2); | ||
| 66 | } | ||
| 67 | |||
| 68 | template <typename KeyType> | ||
| 69 | bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { | ||
| 70 | return !operator==(lhs, rhs); | ||
| 71 | } | ||
| 72 | |||
| 73 | } // namespace Core::Crypto | ||
| 74 | |||
| 75 | namespace std { | ||
| 76 | template <typename KeyType> | ||
| 77 | struct hash<Core::Crypto::KeyIndex<KeyType>> { | ||
| 78 | size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const { | ||
| 79 | using std::hash; | ||
| 80 | |||
| 81 | return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^ | ||
| 82 | (hash<u64>()(k.field2) << 1); | ||
| 83 | } | ||
| 84 | }; | ||
| 85 | } // namespace std | ||
| 86 | |||
| 87 | namespace Core::Crypto { | ||
| 88 | |||
| 89 | std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); | ||
| 90 | std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); | ||
| 91 | |||
| 92 | class KeyManager { | ||
| 93 | public: | ||
| 94 | KeyManager(); | ||
| 95 | |||
| 96 | bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||
| 97 | bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||
| 98 | |||
| 99 | Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||
| 100 | Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||
| 101 | |||
| 102 | void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | ||
| 103 | void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | ||
| 104 | |||
| 105 | static bool KeyFileExists(bool title); | ||
| 106 | |||
| 107 | private: | ||
| 108 | std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; | ||
| 109 | std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; | ||
| 110 | |||
| 111 | bool dev_mode; | ||
| 112 | void LoadFromFile(std::string_view filename, bool is_title_keys); | ||
| 113 | void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename, | ||
| 114 | bool title); | ||
| 115 | |||
| 116 | static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id; | ||
| 117 | static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id; | ||
| 118 | }; | ||
| 119 | } // namespace Core::Crypto | ||
diff --git a/src/core/crypto/sha_util.cpp b/src/core/crypto/sha_util.cpp new file mode 100644 index 000000000..180008a85 --- /dev/null +++ b/src/core/crypto/sha_util.cpp | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | 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 000000000..fa3fa9d33 --- /dev/null +++ b/src/core/crypto/sha_util.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "common/assert.h" | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | #include "key_manager.h" | ||
| 10 | #include "mbedtls/cipher.h" | ||
| 11 | |||
| 12 | namespace Crypto { | ||
| 13 | typedef std::array<u8, 0x20> SHA256Hash; | ||
| 14 | |||
| 15 | inline SHA256Hash operator"" _HASH(const char* data, size_t len) { | ||
| 16 | if (len != 0x40) | ||
| 17 | return {}; | ||
| 18 | } | ||
| 19 | |||
| 20 | } // 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 000000000..395eea8ae --- /dev/null +++ b/src/core/file_sys/card_image.cpp | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <array> | ||
| 6 | #include <string> | ||
| 7 | #include <core/loader/loader.h> | ||
| 8 | #include "core/file_sys/card_image.h" | ||
| 9 | #include "core/file_sys/partition_filesystem.h" | ||
| 10 | #include "core/file_sys/vfs_offset.h" | ||
| 11 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { | ||
| 15 | if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||
| 16 | status = Loader::ResultStatus::ErrorInvalidFormat; | ||
| 17 | return; | ||
| 18 | } | ||
| 19 | |||
| 20 | if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||
| 21 | status = Loader::ResultStatus::ErrorInvalidFormat; | ||
| 22 | return; | ||
| 23 | } | ||
| 24 | |||
| 25 | PartitionFilesystem main_hfs( | ||
| 26 | std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset)); | ||
| 27 | |||
| 28 | if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { | ||
| 29 | status = main_hfs.GetStatus(); | ||
| 30 | return; | ||
| 31 | } | ||
| 32 | |||
| 33 | static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", | ||
| 34 | "logo"}; | ||
| 35 | |||
| 36 | for (XCIPartition partition : | ||
| 37 | {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { | ||
| 38 | auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); | ||
| 39 | if (raw != nullptr) | ||
| 40 | partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); | ||
| 41 | } | ||
| 42 | |||
| 43 | auto result = AddNCAFromPartition(XCIPartition::Secure); | ||
| 44 | if (result != Loader::ResultStatus::Success) { | ||
| 45 | status = result; | ||
| 46 | return; | ||
| 47 | } | ||
| 48 | |||
| 49 | result = AddNCAFromPartition(XCIPartition::Update); | ||
| 50 | if (result != Loader::ResultStatus::Success) { | ||
| 51 | status = result; | ||
| 52 | return; | ||
| 53 | } | ||
| 54 | |||
| 55 | result = AddNCAFromPartition(XCIPartition::Normal); | ||
| 56 | if (result != Loader::ResultStatus::Success) { | ||
| 57 | status = result; | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (GetFormatVersion() >= 0x2) { | ||
| 62 | result = AddNCAFromPartition(XCIPartition::Logo); | ||
| 63 | if (result != Loader::ResultStatus::Success) { | ||
| 64 | status = result; | ||
| 65 | return; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | status = Loader::ResultStatus::Success; | ||
| 70 | } | ||
| 71 | |||
| 72 | Loader::ResultStatus XCI::GetStatus() const { | ||
| 73 | return status; | ||
| 74 | } | ||
| 75 | |||
| 76 | VirtualDir XCI::GetPartition(XCIPartition partition) const { | ||
| 77 | return partitions[static_cast<size_t>(partition)]; | ||
| 78 | } | ||
| 79 | |||
| 80 | VirtualDir XCI::GetSecurePartition() const { | ||
| 81 | return GetPartition(XCIPartition::Secure); | ||
| 82 | } | ||
| 83 | |||
| 84 | VirtualDir XCI::GetNormalPartition() const { | ||
| 85 | return GetPartition(XCIPartition::Normal); | ||
| 86 | } | ||
| 87 | |||
| 88 | VirtualDir XCI::GetUpdatePartition() const { | ||
| 89 | return GetPartition(XCIPartition::Update); | ||
| 90 | } | ||
| 91 | |||
| 92 | VirtualDir XCI::GetLogoPartition() const { | ||
| 93 | return GetPartition(XCIPartition::Logo); | ||
| 94 | } | ||
| 95 | |||
| 96 | std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { | ||
| 97 | const auto iter = | ||
| 98 | std::find_if(ncas.begin(), ncas.end(), | ||
| 99 | [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; }); | ||
| 100 | return iter == ncas.end() ? nullptr : *iter; | ||
| 101 | } | ||
| 102 | |||
| 103 | VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { | ||
| 104 | auto nca = GetNCAByType(type); | ||
| 105 | if (nca != nullptr) | ||
| 106 | return nca->GetBaseFile(); | ||
| 107 | return nullptr; | ||
| 108 | } | ||
| 109 | |||
| 110 | std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const { | ||
| 111 | return {}; | ||
| 112 | } | ||
| 113 | |||
| 114 | std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const { | ||
| 115 | return std::vector<std::shared_ptr<VfsDirectory>>(); | ||
| 116 | } | ||
| 117 | |||
| 118 | std::string XCI::GetName() const { | ||
| 119 | return file->GetName(); | ||
| 120 | } | ||
| 121 | |||
| 122 | std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const { | ||
| 123 | return file->GetContainingDirectory(); | ||
| 124 | } | ||
| 125 | |||
| 126 | bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||
| 127 | return false; | ||
| 128 | } | ||
| 129 | |||
| 130 | Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | ||
| 131 | if (partitions[static_cast<size_t>(part)] == nullptr) { | ||
| 132 | return Loader::ResultStatus::ErrorInvalidFormat; | ||
| 133 | } | ||
| 134 | |||
| 135 | for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { | ||
| 136 | if (file->GetExtension() != "nca") | ||
| 137 | continue; | ||
| 138 | auto nca = std::make_shared<NCA>(file); | ||
| 139 | if (nca->GetStatus() == Loader::ResultStatus::Success) | ||
| 140 | ncas.push_back(std::move(nca)); | ||
| 141 | } | ||
| 142 | |||
| 143 | return Loader::ResultStatus::Success; | ||
| 144 | } | ||
| 145 | |||
| 146 | u8 XCI::GetFormatVersion() const { | ||
| 147 | return GetLogoPartition() == nullptr ? 0x1 : 0x2; | ||
| 148 | } | ||
| 149 | } // 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 000000000..e089d737c --- /dev/null +++ b/src/core/file_sys/card_image.h | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <vector> | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "common/swap.h" | ||
| 11 | #include "core/file_sys/content_archive.h" | ||
| 12 | #include "core/file_sys/vfs.h" | ||
| 13 | #include "core/loader/loader.h" | ||
| 14 | |||
| 15 | namespace FileSys { | ||
| 16 | |||
| 17 | enum class GamecardSize : u8 { | ||
| 18 | S_1GB = 0xFA, | ||
| 19 | S_2GB = 0xF8, | ||
| 20 | S_4GB = 0xF0, | ||
| 21 | S_8GB = 0xE0, | ||
| 22 | S_16GB = 0xE1, | ||
| 23 | S_32GB = 0xE2, | ||
| 24 | }; | ||
| 25 | |||
| 26 | struct GamecardInfo { | ||
| 27 | std::array<u8, 0x70> data; | ||
| 28 | }; | ||
| 29 | static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); | ||
| 30 | |||
| 31 | struct GamecardHeader { | ||
| 32 | std::array<u8, 0x100> signature; | ||
| 33 | u32_le magic; | ||
| 34 | u32_le secure_area_start; | ||
| 35 | u32_le backup_area_start; | ||
| 36 | u8 kek_index; | ||
| 37 | GamecardSize size; | ||
| 38 | u8 header_version; | ||
| 39 | u8 flags; | ||
| 40 | u64_le package_id; | ||
| 41 | u64_le valid_data_end; | ||
| 42 | u128 info_iv; | ||
| 43 | u64_le hfs_offset; | ||
| 44 | u64_le hfs_size; | ||
| 45 | std::array<u8, 0x20> hfs_header_hash; | ||
| 46 | std::array<u8, 0x20> initial_data_hash; | ||
| 47 | u32_le secure_mode_flag; | ||
| 48 | u32_le title_key_flag; | ||
| 49 | u32_le key_flag; | ||
| 50 | u32_le normal_area_end; | ||
| 51 | GamecardInfo info; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); | ||
| 54 | |||
| 55 | enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; | ||
| 56 | |||
| 57 | class XCI : public ReadOnlyVfsDirectory { | ||
| 58 | public: | ||
| 59 | explicit XCI(VirtualFile file); | ||
| 60 | |||
| 61 | Loader::ResultStatus GetStatus() const; | ||
| 62 | |||
| 63 | u8 GetFormatVersion() const; | ||
| 64 | |||
| 65 | VirtualDir GetPartition(XCIPartition partition) const; | ||
| 66 | VirtualDir GetSecurePartition() const; | ||
| 67 | VirtualDir GetNormalPartition() const; | ||
| 68 | VirtualDir GetUpdatePartition() const; | ||
| 69 | VirtualDir GetLogoPartition() const; | ||
| 70 | |||
| 71 | std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; | ||
| 72 | VirtualFile GetNCAFileByType(NCAContentType type) const; | ||
| 73 | |||
| 74 | std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||
| 75 | |||
| 76 | std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||
| 77 | |||
| 78 | std::string GetName() const override; | ||
| 79 | |||
| 80 | std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||
| 81 | |||
| 82 | protected: | ||
| 83 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||
| 84 | |||
| 85 | private: | ||
| 86 | Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | ||
| 87 | |||
| 88 | VirtualFile file; | ||
| 89 | GamecardHeader header{}; | ||
| 90 | |||
| 91 | Loader::ResultStatus status; | ||
| 92 | |||
| 93 | std::vector<VirtualDir> partitions; | ||
| 94 | std::vector<std::shared_ptr<NCA>> ncas; | ||
| 95 | }; | ||
| 96 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 61cb0bbe3..79e70f6ef 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp | |||
| @@ -4,12 +4,14 @@ | |||
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <utility> | 6 | #include <utility> |
| 7 | 7 | #include <boost/optional.hpp> | |
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "core/crypto/aes_util.h" | ||
| 10 | #include "core/crypto/ctr_encryption_layer.h" | ||
| 9 | #include "core/file_sys/content_archive.h" | 11 | #include "core/file_sys/content_archive.h" |
| 12 | #include "core/file_sys/romfs.h" | ||
| 10 | #include "core/file_sys/vfs_offset.h" | 13 | #include "core/file_sys/vfs_offset.h" |
| 11 | #include "core/loader/loader.h" | 14 | #include "core/loader/loader.h" |
| 12 | #include "romfs.h" | ||
| 13 | 15 | ||
| 14 | namespace FileSys { | 16 | namespace FileSys { |
| 15 | 17 | ||
| @@ -29,11 +31,19 @@ enum class NCASectionFilesystemType : u8 { | |||
| 29 | struct NCASectionHeaderBlock { | 31 | struct NCASectionHeaderBlock { |
| 30 | INSERT_PADDING_BYTES(3); | 32 | INSERT_PADDING_BYTES(3); |
| 31 | NCASectionFilesystemType filesystem_type; | 33 | NCASectionFilesystemType filesystem_type; |
| 32 | u8 crypto_type; | 34 | NCASectionCryptoType crypto_type; |
| 33 | INSERT_PADDING_BYTES(3); | 35 | INSERT_PADDING_BYTES(3); |
| 34 | }; | 36 | }; |
| 35 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | 37 | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); |
| 36 | 38 | ||
| 39 | struct NCASectionRaw { | ||
| 40 | NCASectionHeaderBlock header; | ||
| 41 | std::array<u8, 0x138> block_data; | ||
| 42 | std::array<u8, 0x8> section_ctr; | ||
| 43 | INSERT_PADDING_BYTES(0xB8); | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||
| 46 | |||
| 37 | struct PFS0Superblock { | 47 | struct PFS0Superblock { |
| 38 | NCASectionHeaderBlock header_block; | 48 | NCASectionHeaderBlock header_block; |
| 39 | std::array<u8, 0x20> hash; | 49 | std::array<u8, 0x20> hash; |
| @@ -43,67 +53,170 @@ struct PFS0Superblock { | |||
| 43 | u64_le hash_table_size; | 53 | u64_le hash_table_size; |
| 44 | u64_le pfs0_header_offset; | 54 | u64_le pfs0_header_offset; |
| 45 | u64_le pfs0_size; | 55 | u64_le pfs0_size; |
| 46 | INSERT_PADDING_BYTES(432); | 56 | INSERT_PADDING_BYTES(0x1B0); |
| 47 | }; | 57 | }; |
| 48 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | 58 | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); |
| 49 | 59 | ||
| 50 | struct RomFSSuperblock { | 60 | struct RomFSSuperblock { |
| 51 | NCASectionHeaderBlock header_block; | 61 | NCASectionHeaderBlock header_block; |
| 52 | IVFCHeader ivfc; | 62 | IVFCHeader ivfc; |
| 63 | INSERT_PADDING_BYTES(0x118); | ||
| 53 | }; | 64 | }; |
| 54 | static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); | 65 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); |
| 66 | |||
| 67 | union NCASectionHeader { | ||
| 68 | NCASectionRaw raw; | ||
| 69 | PFS0Superblock pfs0; | ||
| 70 | RomFSSuperblock romfs; | ||
| 71 | }; | ||
| 72 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||
| 73 | |||
| 74 | bool IsValidNCA(const NCAHeader& header) { | ||
| 75 | // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||
| 76 | return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 77 | } | ||
| 78 | |||
| 79 | boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||
| 80 | u8 master_key_id = header.crypto_type; | ||
| 81 | if (header.crypto_type_2 > master_key_id) | ||
| 82 | master_key_id = header.crypto_type_2; | ||
| 83 | if (master_key_id > 0) | ||
| 84 | --master_key_id; | ||
| 85 | |||
| 86 | if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) | ||
| 87 | return boost::none; | ||
| 88 | |||
| 89 | std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||
| 90 | Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||
| 91 | keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||
| 92 | Core::Crypto::Mode::ECB); | ||
| 93 | cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||
| 94 | |||
| 95 | Core::Crypto::Key128 out; | ||
| 96 | if (type == NCASectionCryptoType::XTS) | ||
| 97 | std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||
| 98 | else if (type == NCASectionCryptoType::CTR) | ||
| 99 | std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||
| 100 | else | ||
| 101 | LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||
| 102 | static_cast<u8>(type)); | ||
| 103 | u128 out_128{}; | ||
| 104 | memcpy(out_128.data(), out.data(), 16); | ||
| 105 | LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||
| 106 | master_key_id, header.key_index, out_128[1], out_128[0]); | ||
| 107 | |||
| 108 | return out; | ||
| 109 | } | ||
| 110 | |||
| 111 | VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const { | ||
| 112 | if (!encrypted) | ||
| 113 | return in; | ||
| 114 | |||
| 115 | switch (header.raw.header.crypto_type) { | ||
| 116 | case NCASectionCryptoType::NONE: | ||
| 117 | LOG_DEBUG(Crypto, "called with mode=NONE"); | ||
| 118 | return in; | ||
| 119 | case NCASectionCryptoType::CTR: | ||
| 120 | LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||
| 121 | { | ||
| 122 | const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||
| 123 | if (key == boost::none) | ||
| 124 | return nullptr; | ||
| 125 | auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( | ||
| 126 | std::move(in), key.value(), starting_offset); | ||
| 127 | std::vector<u8> iv(16); | ||
| 128 | for (u8 i = 0; i < 8; ++i) | ||
| 129 | iv[i] = header.raw.section_ctr[0x8 - i - 1]; | ||
| 130 | out->SetIV(iv); | ||
| 131 | return std::static_pointer_cast<VfsFile>(out); | ||
| 132 | } | ||
| 133 | case NCASectionCryptoType::XTS: | ||
| 134 | // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption. | ||
| 135 | default: | ||
| 136 | LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||
| 137 | static_cast<u8>(header.raw.header.crypto_type)); | ||
| 138 | return nullptr; | ||
| 139 | } | ||
| 140 | } | ||
| 55 | 141 | ||
| 56 | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { | 142 | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { |
| 57 | if (sizeof(NCAHeader) != file->ReadObject(&header)) | 143 | if (sizeof(NCAHeader) != file->ReadObject(&header)) |
| 58 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | 144 | LOG_ERROR(Loader, "File reader errored out during header read."); |
| 145 | |||
| 146 | encrypted = false; | ||
| 59 | 147 | ||
| 60 | if (!IsValidNCA(header)) { | 148 | if (!IsValidNCA(header)) { |
| 61 | status = Loader::ResultStatus::ErrorInvalidFormat; | 149 | NCAHeader dec_header{}; |
| 62 | return; | 150 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( |
| 151 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 152 | cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||
| 153 | Core::Crypto::Op::Decrypt); | ||
| 154 | if (IsValidNCA(dec_header)) { | ||
| 155 | header = dec_header; | ||
| 156 | encrypted = true; | ||
| 157 | } else { | ||
| 158 | if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) | ||
| 159 | status = Loader::ResultStatus::ErrorMissingKeys; | ||
| 160 | else | ||
| 161 | status = Loader::ResultStatus::ErrorDecrypting; | ||
| 162 | return; | ||
| 163 | } | ||
| 63 | } | 164 | } |
| 64 | 165 | ||
| 65 | std::ptrdiff_t number_sections = | 166 | const std::ptrdiff_t number_sections = |
| 66 | std::count_if(std::begin(header.section_tables), std::end(header.section_tables), | 167 | std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |
| 67 | [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); | 168 | [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); |
| 68 | 169 | ||
| 170 | std::vector<NCASectionHeader> sections(number_sections); | ||
| 171 | const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||
| 172 | |||
| 173 | if (encrypted) { | ||
| 174 | auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||
| 175 | Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||
| 176 | keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||
| 177 | cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||
| 178 | Core::Crypto::Op::Decrypt); | ||
| 179 | } else { | ||
| 180 | file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||
| 181 | } | ||
| 182 | |||
| 69 | for (std::ptrdiff_t i = 0; i < number_sections; ++i) { | 183 | for (std::ptrdiff_t i = 0; i < number_sections; ++i) { |
| 70 | // Seek to beginning of this section. | 184 | auto section = sections[i]; |
| 71 | NCASectionHeaderBlock block{}; | ||
| 72 | if (sizeof(NCASectionHeaderBlock) != | ||
| 73 | file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||
| 74 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||
| 75 | |||
| 76 | if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 77 | RomFSSuperblock sb{}; | ||
| 78 | if (sizeof(RomFSSuperblock) != | ||
| 79 | file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | ||
| 80 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | ||
| 81 | 185 | ||
| 186 | if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||
| 82 | const size_t romfs_offset = | 187 | const size_t romfs_offset = |
| 83 | header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + | 188 | header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + |
| 84 | sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | 189 | section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; |
| 85 | const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | 190 | const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; |
| 86 | files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); | 191 | auto dec = |
| 87 | romfs = files.back(); | 192 | Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), |
| 88 | } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { | 193 | romfs_offset); |
| 89 | PFS0Superblock sb{}; | 194 | if (dec != nullptr) { |
| 90 | // Seek back to beginning of this section. | 195 | files.push_back(std::move(dec)); |
| 91 | if (sizeof(PFS0Superblock) != | 196 | romfs = files.back(); |
| 92 | file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) | 197 | } else { |
| 93 | LOG_CRITICAL(Loader, "File reader errored out during header read."); | 198 | status = Loader::ResultStatus::ErrorMissingKeys; |
| 94 | 199 | return; | |
| 200 | } | ||
| 201 | } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||
| 95 | u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * | 202 | u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * |
| 96 | MEDIA_OFFSET_MULTIPLIER) + | 203 | MEDIA_OFFSET_MULTIPLIER) + |
| 97 | sb.pfs0_header_offset; | 204 | section.pfs0.pfs0_header_offset; |
| 98 | u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - | 205 | u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - |
| 99 | header.section_tables[i].media_offset); | 206 | header.section_tables[i].media_offset); |
| 100 | auto npfs = std::make_shared<PartitionFilesystem>( | 207 | auto dec = |
| 101 | std::make_shared<OffsetVfsFile>(file, size, offset)); | 208 | Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); |
| 209 | if (dec != nullptr) { | ||
| 210 | auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||
| 102 | 211 | ||
| 103 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { | 212 | if (npfs->GetStatus() == Loader::ResultStatus::Success) { |
| 104 | dirs.emplace_back(npfs); | 213 | dirs.push_back(std::move(npfs)); |
| 105 | if (IsDirectoryExeFS(dirs.back())) | 214 | if (IsDirectoryExeFS(dirs.back())) |
| 106 | exefs = dirs.back(); | 215 | exefs = dirs.back(); |
| 216 | } | ||
| 217 | } else { | ||
| 218 | status = Loader::ResultStatus::ErrorMissingKeys; | ||
| 219 | return; | ||
| 107 | } | 220 | } |
| 108 | } | 221 | } |
| 109 | } | 222 | } |
| @@ -153,6 +266,10 @@ VirtualDir NCA::GetExeFS() const { | |||
| 153 | return exefs; | 266 | return exefs; |
| 154 | } | 267 | } |
| 155 | 268 | ||
| 269 | VirtualFile NCA::GetBaseFile() const { | ||
| 270 | return file; | ||
| 271 | } | ||
| 272 | |||
| 156 | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | 273 | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { |
| 157 | return false; | 274 | return false; |
| 158 | } | 275 | } |
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 0b8b9db61..6492163b5 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h | |||
| @@ -8,14 +8,18 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include <vector> | 10 | #include <vector> |
| 11 | 11 | #include <boost/optional.hpp> | |
| 12 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "common/swap.h" | 14 | #include "common/swap.h" |
| 15 | #include "core/crypto/key_manager.h" | ||
| 15 | #include "core/file_sys/partition_filesystem.h" | 16 | #include "core/file_sys/partition_filesystem.h" |
| 17 | #include "core/loader/loader.h" | ||
| 16 | 18 | ||
| 17 | namespace FileSys { | 19 | namespace FileSys { |
| 18 | 20 | ||
| 21 | union NCASectionHeader; | ||
| 22 | |||
| 19 | enum class NCAContentType : u8 { | 23 | enum class NCAContentType : u8 { |
| 20 | Program = 0, | 24 | Program = 0, |
| 21 | Meta = 1, | 25 | Meta = 1, |
| @@ -24,6 +28,13 @@ enum class NCAContentType : u8 { | |||
| 24 | Data = 4, | 28 | Data = 4, |
| 25 | }; | 29 | }; |
| 26 | 30 | ||
| 31 | enum class NCASectionCryptoType : u8 { | ||
| 32 | NONE = 1, | ||
| 33 | XTS = 2, | ||
| 34 | CTR = 3, | ||
| 35 | BKTR = 4, | ||
| 36 | }; | ||
| 37 | |||
| 27 | struct NCASectionTableEntry { | 38 | struct NCASectionTableEntry { |
| 28 | u32_le media_offset; | 39 | u32_le media_offset; |
| 29 | u32_le media_end_offset; | 40 | u32_le media_end_offset; |
| @@ -48,7 +59,7 @@ struct NCAHeader { | |||
| 48 | std::array<u8, 0x10> rights_id; | 59 | std::array<u8, 0x10> rights_id; |
| 49 | std::array<NCASectionTableEntry, 0x4> section_tables; | 60 | std::array<NCASectionTableEntry, 0x4> section_tables; |
| 50 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; | 61 | std::array<std::array<u8, 0x20>, 0x4> hash_tables; |
| 51 | std::array<std::array<u8, 0x10>, 0x4> key_area; | 62 | std::array<u8, 0x40> key_area; |
| 52 | INSERT_PADDING_BYTES(0xC0); | 63 | INSERT_PADDING_BYTES(0xC0); |
| 53 | }; | 64 | }; |
| 54 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | 65 | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); |
| @@ -58,10 +69,7 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { | |||
| 58 | return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; | 69 | return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; |
| 59 | } | 70 | } |
| 60 | 71 | ||
| 61 | inline bool IsValidNCA(const NCAHeader& header) { | 72 | bool IsValidNCA(const NCAHeader& header); |
| 62 | return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || | ||
| 63 | header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||
| 64 | } | ||
| 65 | 73 | ||
| 66 | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. | 74 | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. |
| 67 | // After construction, use GetStatus to determine if the file is valid and ready to be used. | 75 | // After construction, use GetStatus to determine if the file is valid and ready to be used. |
| @@ -81,10 +89,15 @@ public: | |||
| 81 | VirtualFile GetRomFS() const; | 89 | VirtualFile GetRomFS() const; |
| 82 | VirtualDir GetExeFS() const; | 90 | VirtualDir GetExeFS() const; |
| 83 | 91 | ||
| 92 | VirtualFile GetBaseFile() const; | ||
| 93 | |||
| 84 | protected: | 94 | protected: |
| 85 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | 95 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; |
| 86 | 96 | ||
| 87 | private: | 97 | private: |
| 98 | boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||
| 99 | VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; | ||
| 100 | |||
| 88 | std::vector<VirtualDir> dirs; | 101 | std::vector<VirtualDir> dirs; |
| 89 | std::vector<VirtualFile> files; | 102 | std::vector<VirtualFile> files; |
| 90 | 103 | ||
| @@ -95,6 +108,10 @@ private: | |||
| 95 | NCAHeader header{}; | 108 | NCAHeader header{}; |
| 96 | 109 | ||
| 97 | Loader::ResultStatus status{}; | 110 | Loader::ResultStatus status{}; |
| 111 | |||
| 112 | bool encrypted; | ||
| 113 | |||
| 114 | Core::Crypto::KeyManager keys; | ||
| 98 | }; | 115 | }; |
| 99 | 116 | ||
| 100 | } // namespace FileSys | 117 | } // namespace FileSys |
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 84a6a7397..dae1c16ef 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp | |||
| @@ -285,6 +285,26 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { | |||
| 285 | return false; | 285 | return false; |
| 286 | } | 286 | } |
| 287 | 287 | ||
| 288 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { | ||
| 289 | if (file1->GetSize() != file2->GetSize()) | ||
| 290 | return false; | ||
| 291 | |||
| 292 | std::vector<u8> f1_v(block_size); | ||
| 293 | std::vector<u8> f2_v(block_size); | ||
| 294 | for (size_t i = 0; i < file1->GetSize(); i += block_size) { | ||
| 295 | auto f1_vs = file1->Read(f1_v.data(), block_size, i); | ||
| 296 | auto f2_vs = file2->Read(f2_v.data(), block_size, i); | ||
| 297 | |||
| 298 | if (f1_vs != f2_vs) | ||
| 299 | return false; | ||
| 300 | auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); | ||
| 301 | if (iters.first != f1_v.end() && iters.second != f2_v.end()) | ||
| 302 | return false; | ||
| 303 | } | ||
| 304 | |||
| 305 | return true; | ||
| 306 | } | ||
| 307 | |||
| 288 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | 308 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { |
| 289 | if (src == nullptr || dest == nullptr) | 309 | if (src == nullptr || dest == nullptr) |
| 290 | return false; | 310 | return false; |
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index cf871edd6..fab9e2b45 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h | |||
| @@ -245,6 +245,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory { | |||
| 245 | bool Rename(std::string_view name) override; | 245 | bool Rename(std::string_view name) override; |
| 246 | }; | 246 | }; |
| 247 | 247 | ||
| 248 | // Compare the two files, byte-for-byte, in increments specificed by block_size | ||
| 249 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); | ||
| 250 | |||
| 248 | // A method that copies the raw data between two different implementations of VirtualFile. If you | 251 | // A method that copies the raw data between two different implementations of VirtualFile. If you |
| 249 | // are using the same implementation, it is probably better to use the Copy method in the parent | 252 | // are using the same implementation, it is probably better to use the Copy method in the parent |
| 250 | // directory of src/dest. | 253 | // directory of src/dest. |
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index cbc4177c6..57e6c0365 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "core/loader/nca.h" | 13 | #include "core/loader/nca.h" |
| 14 | #include "core/loader/nro.h" | 14 | #include "core/loader/nro.h" |
| 15 | #include "core/loader/nso.h" | 15 | #include "core/loader/nso.h" |
| 16 | #include "core/loader/xci.h" | ||
| 16 | 17 | ||
| 17 | namespace Loader { | 18 | namespace Loader { |
| 18 | 19 | ||
| @@ -35,6 +36,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { | |||
| 35 | CHECK_TYPE(NSO) | 36 | CHECK_TYPE(NSO) |
| 36 | CHECK_TYPE(NRO) | 37 | CHECK_TYPE(NRO) |
| 37 | CHECK_TYPE(NCA) | 38 | CHECK_TYPE(NCA) |
| 39 | CHECK_TYPE(XCI) | ||
| 38 | 40 | ||
| 39 | #undef CHECK_TYPE | 41 | #undef CHECK_TYPE |
| 40 | 42 | ||
| @@ -60,6 +62,8 @@ FileType GuessFromFilename(const std::string& name) { | |||
| 60 | return FileType::NSO; | 62 | return FileType::NSO; |
| 61 | if (extension == "nca") | 63 | if (extension == "nca") |
| 62 | return FileType::NCA; | 64 | return FileType::NCA; |
| 65 | if (extension == "xci") | ||
| 66 | return FileType::XCI; | ||
| 63 | 67 | ||
| 64 | return FileType::Unknown; | 68 | return FileType::Unknown; |
| 65 | } | 69 | } |
| @@ -74,6 +78,8 @@ const char* GetFileTypeString(FileType type) { | |||
| 74 | return "NSO"; | 78 | return "NSO"; |
| 75 | case FileType::NCA: | 79 | case FileType::NCA: |
| 76 | return "NCA"; | 80 | return "NCA"; |
| 81 | case FileType::XCI: | ||
| 82 | return "XCI"; | ||
| 77 | case FileType::DeconstructedRomDirectory: | 83 | case FileType::DeconstructedRomDirectory: |
| 78 | return "Directory"; | 84 | return "Directory"; |
| 79 | case FileType::Error: | 85 | case FileType::Error: |
| @@ -111,6 +117,9 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT | |||
| 111 | case FileType::NCA: | 117 | case FileType::NCA: |
| 112 | return std::make_unique<AppLoader_NCA>(std::move(file)); | 118 | return std::make_unique<AppLoader_NCA>(std::move(file)); |
| 113 | 119 | ||
| 120 | case FileType::XCI: | ||
| 121 | return std::make_unique<AppLoader_XCI>(std::move(file)); | ||
| 122 | |||
| 114 | // NX deconstructed ROM directory. | 123 | // NX deconstructed ROM directory. |
| 115 | case FileType::DeconstructedRomDirectory: | 124 | case FileType::DeconstructedRomDirectory: |
| 116 | return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); | 125 | return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 3ca6bcf8b..e69ab85ef 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -31,6 +31,7 @@ enum class FileType { | |||
| 31 | NSO, | 31 | NSO, |
| 32 | NRO, | 32 | NRO, |
| 33 | NCA, | 33 | NCA, |
| 34 | XCI, | ||
| 34 | DeconstructedRomDirectory, | 35 | DeconstructedRomDirectory, |
| 35 | }; | 36 | }; |
| 36 | 37 | ||
| @@ -72,7 +73,8 @@ enum class ResultStatus { | |||
| 72 | ErrorNotUsed, | 73 | ErrorNotUsed, |
| 73 | ErrorAlreadyLoaded, | 74 | ErrorAlreadyLoaded, |
| 74 | ErrorMemoryAllocationFailed, | 75 | ErrorMemoryAllocationFailed, |
| 75 | ErrorEncrypted, | 76 | ErrorMissingKeys, |
| 77 | ErrorDecrypting, | ||
| 76 | ErrorUnsupportedArch, | 78 | ErrorUnsupportedArch, |
| 77 | }; | 79 | }; |
| 78 | 80 | ||
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index c80df23be..a1f8235d1 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -25,12 +25,10 @@ namespace Loader { | |||
| 25 | AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | 25 | AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} |
| 26 | 26 | ||
| 27 | FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { | 27 | FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { |
| 28 | // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. | 28 | FileSys::NCA nca(file); |
| 29 | FileSys::NCAHeader header{}; | ||
| 30 | if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header)) | ||
| 31 | return FileType::Error; | ||
| 32 | 29 | ||
| 33 | if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) | 30 | if (nca.GetStatus() == ResultStatus::Success && |
| 31 | nca.GetType() == FileSys::NCAContentType::Program) | ||
| 34 | return FileType::NCA; | 32 | return FileType::NCA; |
| 35 | 33 | ||
| 36 | return FileType::Error; | 34 | return FileType::Error; |
| @@ -98,12 +96,21 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||
| 98 | } | 96 | } |
| 99 | 97 | ||
| 100 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | 98 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { |
| 101 | if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) | 99 | if (nca == nullptr) |
| 100 | return ResultStatus::ErrorNotLoaded; | ||
| 101 | if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) | ||
| 102 | return ResultStatus::ErrorNotUsed; | 102 | return ResultStatus::ErrorNotUsed; |
| 103 | dir = nca->GetRomFS(); | 103 | dir = nca->GetRomFS(); |
| 104 | return ResultStatus::Success; | 104 | return ResultStatus::Success; |
| 105 | } | 105 | } |
| 106 | 106 | ||
| 107 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | ||
| 108 | if (nca == nullptr) | ||
| 109 | return ResultStatus::ErrorNotLoaded; | ||
| 110 | out_program_id = nca->GetTitleId(); | ||
| 111 | return ResultStatus::Success; | ||
| 112 | } | ||
| 113 | |||
| 107 | AppLoader_NCA::~AppLoader_NCA() = default; | 114 | AppLoader_NCA::~AppLoader_NCA() = default; |
| 108 | 115 | ||
| 109 | } // namespace Loader | 116 | } // namespace Loader |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 2edd81cb9..e14d618b3 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -33,6 +33,8 @@ public: | |||
| 33 | 33 | ||
| 34 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 34 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 35 | 35 | ||
| 36 | ResultStatus ReadProgramId(u64& out_program_id) override; | ||
| 37 | |||
| 36 | ~AppLoader_NCA(); | 38 | ~AppLoader_NCA(); |
| 37 | 39 | ||
| 38 | private: | 40 | private: |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp new file mode 100644 index 000000000..eb4dee2c2 --- /dev/null +++ b/src/core/loader/xci.cpp | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <vector> | ||
| 6 | |||
| 7 | #include "common/file_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/string_util.h" | ||
| 10 | #include "common/swap.h" | ||
| 11 | #include "core/core.h" | ||
| 12 | #include "core/file_sys/content_archive.h" | ||
| 13 | #include "core/file_sys/control_metadata.h" | ||
| 14 | #include "core/file_sys/program_metadata.h" | ||
| 15 | #include "core/file_sys/romfs.h" | ||
| 16 | #include "core/gdbstub/gdbstub.h" | ||
| 17 | #include "core/hle/kernel/process.h" | ||
| 18 | #include "core/hle/kernel/resource_limit.h" | ||
| 19 | #include "core/hle/service/filesystem/filesystem.h" | ||
| 20 | #include "core/loader/nso.h" | ||
| 21 | #include "core/loader/xci.h" | ||
| 22 | #include "core/memory.h" | ||
| 23 | |||
| 24 | namespace Loader { | ||
| 25 | |||
| 26 | AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) | ||
| 27 | : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), | ||
| 28 | nca_loader(std::make_unique<AppLoader_NCA>( | ||
| 29 | xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} | ||
| 30 | |||
| 31 | AppLoader_XCI::~AppLoader_XCI() = default; | ||
| 32 | |||
| 33 | FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { | ||
| 34 | FileSys::XCI xci(file); | ||
| 35 | |||
| 36 | if (xci.GetStatus() == ResultStatus::Success && | ||
| 37 | xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && | ||
| 38 | AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == | ||
| 39 | FileType::NCA) { | ||
| 40 | return FileType::XCI; | ||
| 41 | } | ||
| 42 | |||
| 43 | return FileType::Error; | ||
| 44 | } | ||
| 45 | |||
| 46 | ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ||
| 47 | if (is_loaded) { | ||
| 48 | return ResultStatus::ErrorAlreadyLoaded; | ||
| 49 | } | ||
| 50 | |||
| 51 | if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { | ||
| 52 | if (!Core::Crypto::KeyManager::KeyFileExists(false)) | ||
| 53 | return ResultStatus::ErrorMissingKeys; | ||
| 54 | return ResultStatus::ErrorDecrypting; | ||
| 55 | } | ||
| 56 | |||
| 57 | auto result = nca_loader->Load(process); | ||
| 58 | if (result != ResultStatus::Success) | ||
| 59 | return result; | ||
| 60 | |||
| 61 | is_loaded = true; | ||
| 62 | |||
| 63 | return ResultStatus::Success; | ||
| 64 | } | ||
| 65 | |||
| 66 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { | ||
| 67 | return nca_loader->ReadRomFS(dir); | ||
| 68 | } | ||
| 69 | |||
| 70 | ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { | ||
| 71 | return nca_loader->ReadProgramId(out_program_id); | ||
| 72 | } | ||
| 73 | |||
| 74 | } // namespace Loader | ||
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h new file mode 100644 index 000000000..0dbcfbdf8 --- /dev/null +++ b/src/core/loader/xci.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "core/file_sys/card_image.h" | ||
| 10 | #include "core/loader/loader.h" | ||
| 11 | #include "core/loader/nca.h" | ||
| 12 | |||
| 13 | namespace Loader { | ||
| 14 | |||
| 15 | /// Loads an XCI file | ||
| 16 | class AppLoader_XCI final : public AppLoader { | ||
| 17 | public: | ||
| 18 | explicit AppLoader_XCI(FileSys::VirtualFile file); | ||
| 19 | ~AppLoader_XCI(); | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Returns the type of the file | ||
| 23 | * @param file std::shared_ptr<VfsFile> open file | ||
| 24 | * @return FileType found, or FileType::Error if this loader doesn't know it | ||
| 25 | */ | ||
| 26 | static FileType IdentifyType(const FileSys::VirtualFile& file); | ||
| 27 | |||
| 28 | FileType GetFileType() override { | ||
| 29 | return IdentifyType(file); | ||
| 30 | } | ||
| 31 | |||
| 32 | ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||
| 33 | |||
| 34 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||
| 35 | ResultStatus ReadProgramId(u64& out_program_id) override; | ||
| 36 | |||
| 37 | private: | ||
| 38 | FileSys::ProgramMetadata metadata; | ||
| 39 | |||
| 40 | std::unique_ptr<FileSys::XCI> xci; | ||
| 41 | std::unique_ptr<AppLoader_NCA> nca_loader; | ||
| 42 | }; | ||
| 43 | |||
| 44 | } // namespace Loader | ||
diff --git a/src/core/settings.h b/src/core/settings.h index e826d4235..73dc3061f 100644 --- a/src/core/settings.h +++ b/src/core/settings.h | |||
| @@ -139,6 +139,8 @@ struct Values { | |||
| 139 | 139 | ||
| 140 | std::string log_filter; | 140 | std::string log_filter; |
| 141 | 141 | ||
| 142 | bool use_dev_keys; | ||
| 143 | |||
| 142 | // Audio | 144 | // Audio |
| 143 | std::string sink_id; | 145 | std::string sink_id; |
| 144 | std::string audio_device_id; | 146 | std::string audio_device_id; |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 9d6454bbd..bf469ee73 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -111,6 +111,7 @@ void Config::ReadValues() { | |||
| 111 | 111 | ||
| 112 | qt_config->beginGroup("Miscellaneous"); | 112 | qt_config->beginGroup("Miscellaneous"); |
| 113 | Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); | 113 | Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); |
| 114 | Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool(); | ||
| 114 | qt_config->endGroup(); | 115 | qt_config->endGroup(); |
| 115 | 116 | ||
| 116 | qt_config->beginGroup("Debugging"); | 117 | qt_config->beginGroup("Debugging"); |
| @@ -222,6 +223,7 @@ void Config::SaveValues() { | |||
| 222 | 223 | ||
| 223 | qt_config->beginGroup("Miscellaneous"); | 224 | qt_config->beginGroup("Miscellaneous"); |
| 224 | qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); | 225 | qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); |
| 226 | qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys); | ||
| 225 | qt_config->endGroup(); | 227 | qt_config->endGroup(); |
| 226 | 228 | ||
| 227 | qt_config->beginGroup("Debugging"); | 229 | qt_config->beginGroup("Debugging"); |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 99e6634a1..71e24a9e2 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -365,7 +365,7 @@ void GameList::LoadInterfaceLayout() { | |||
| 365 | item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); | 365 | item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); |
| 366 | } | 366 | } |
| 367 | 367 | ||
| 368 | const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"}; | 368 | const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; |
| 369 | 369 | ||
| 370 | static bool HasSupportedFileExtension(const std::string& file_name) { | 370 | static bool HasSupportedFileExtension(const std::string& file_name) { |
| 371 | QFileInfo file = QFileInfo(file_name.c_str()); | 371 | QFileInfo file = QFileInfo(file_name.c_str()); |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 341867f7b..eac41553a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #include "common/scope_exit.h" | 23 | #include "common/scope_exit.h" |
| 24 | #include "common/string_util.h" | 24 | #include "common/string_util.h" |
| 25 | #include "core/core.h" | 25 | #include "core/core.h" |
| 26 | #include "core/crypto/key_manager.h" | ||
| 26 | #include "core/gdbstub/gdbstub.h" | 27 | #include "core/gdbstub/gdbstub.h" |
| 27 | #include "core/loader/loader.h" | 28 | #include "core/loader/loader.h" |
| 28 | #include "core/settings.h" | 29 | #include "core/settings.h" |
| @@ -424,18 +425,49 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||
| 424 | tr("Could not determine the system mode.")); | 425 | tr("Could not determine the system mode.")); |
| 425 | break; | 426 | break; |
| 426 | 427 | ||
| 427 | case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { | 428 | case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: { |
| 429 | const auto reg_found = Core::Crypto::KeyManager::KeyFileExists(false); | ||
| 430 | const auto title_found = Core::Crypto::KeyManager::KeyFileExists(true); | ||
| 431 | |||
| 432 | std::string file_text; | ||
| 433 | |||
| 434 | if (!reg_found && !title_found) { | ||
| 435 | file_text = "A proper key file (prod.keys, dev.keys, or title.keys) could not be " | ||
| 436 | "found. You will need to dump your keys from your switch to continue."; | ||
| 437 | } else if (reg_found && title_found) { | ||
| 438 | file_text = | ||
| 439 | "Both key files were found in your config directory, but the correct key could" | ||
| 440 | "not be found. You may be missing a titlekey or general key, depending on " | ||
| 441 | "the game."; | ||
| 442 | } else if (reg_found) { | ||
| 443 | file_text = | ||
| 444 | "The regular keys file (prod.keys/dev.keys) was found in your config, but the " | ||
| 445 | "titlekeys file (title.keys) was not. You are either missing the correct " | ||
| 446 | "titlekey or missing a general key required to decrypt the game."; | ||
| 447 | } else { | ||
| 448 | file_text = "The title keys file (title.keys) was found in your config, but " | ||
| 449 | "the regular keys file (prod.keys/dev.keys) was not. Unfortunately, " | ||
| 450 | "having the titlekey is not enough, you need additional general keys " | ||
| 451 | "to properly decrypt the game. You should double-check to make sure " | ||
| 452 | "your keys are correct."; | ||
| 453 | } | ||
| 454 | |||
| 455 | QMessageBox::critical( | ||
| 456 | this, tr("Error while loading ROM!"), | ||
| 457 | tr(("The game you are trying to load is encrypted and the required keys to load " | ||
| 458 | "the game could not be found in your configuration. " + | ||
| 459 | file_text + " Please refer to the yuzu wiki for help.") | ||
| 460 | .c_str())); | ||
| 461 | break; | ||
| 462 | } | ||
| 463 | case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: { | ||
| 428 | QMessageBox::critical( | 464 | QMessageBox::critical( |
| 429 | this, tr("Error while loading ROM!"), | 465 | this, tr("Error while loading ROM!"), |
| 430 | tr("The game that you are trying to load must be decrypted before being used with " | 466 | tr("There was a general error while decrypting the game. This means that the keys " |
| 431 | "yuzu. A real Switch is required.<br/><br/>" | 467 | "necessary were found, but were either incorrect, the game itself was not a " |
| 432 | "For more information on dumping and decrypting games, please see the following " | 468 | "valid game or the game uses an unhandled cryptographic scheme. Please double " |
| 433 | "wiki pages: <ul>" | 469 | "check that you have the correct " |
| 434 | "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " | 470 | "keys.")); |
| 435 | "Cartridges</a></li>" | ||
| 436 | "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping " | ||
| 437 | "Installed Titles</a></li>" | ||
| 438 | "</ul>")); | ||
| 439 | break; | 471 | break; |
| 440 | } | 472 | } |
| 441 | case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: | 473 | case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index c581e9699..9bf26717f 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -119,6 +119,7 @@ void Config::ReadValues() { | |||
| 119 | 119 | ||
| 120 | // Miscellaneous | 120 | // Miscellaneous |
| 121 | Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); | 121 | Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); |
| 122 | Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); | ||
| 122 | 123 | ||
| 123 | // Debugging | 124 | // Debugging |
| 124 | Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); | 125 | 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 5ff6266bf..b23213cf6 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp | |||
| @@ -23,6 +23,7 @@ | |||
| 23 | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | 23 | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" |
| 24 | 24 | ||
| 25 | #include <getopt.h> | 25 | #include <getopt.h> |
| 26 | #include "core/crypto/key_manager.h" | ||
| 26 | #ifndef _MSC_VER | 27 | #ifndef _MSC_VER |
| 27 | #include <unistd.h> | 28 | #include <unistd.h> |
| 28 | #endif | 29 | #endif |
| @@ -71,6 +72,7 @@ static void InitializeLogging() { | |||
| 71 | /// Application entry point | 72 | /// Application entry point |
| 72 | int main(int argc, char** argv) { | 73 | int main(int argc, char** argv) { |
| 73 | Config config; | 74 | Config config; |
| 75 | |||
| 74 | int option_index = 0; | 76 | int option_index = 0; |
| 75 | bool use_gdbstub = Settings::values.use_gdbstub; | 77 | bool use_gdbstub = Settings::values.use_gdbstub; |
| 76 | u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); | 78 | u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); |
| @@ -171,11 +173,15 @@ int main(int argc, char** argv) { | |||
| 171 | case Core::System::ResultStatus::ErrorLoader: | 173 | case Core::System::ResultStatus::ErrorLoader: |
| 172 | LOG_CRITICAL(Frontend, "Failed to load ROM!"); | 174 | LOG_CRITICAL(Frontend, "Failed to load ROM!"); |
| 173 | return -1; | 175 | return -1; |
| 174 | case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: | 176 | case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: |
| 175 | LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " | 177 | LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required " |
| 176 | "being used with yuzu. \n\n For more information on dumping and " | 178 | "could not be found. Please refer to the yuzu wiki for help"); |
| 177 | "decrypting games, please refer to: " | 179 | return -1; |
| 178 | "https://yuzu-emu.org/wiki/dumping-game-cartridges/"); | 180 | case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: |
| 181 | LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a " | ||
| 182 | "general error while decrypting. This could mean that the keys are " | ||
| 183 | "incorrect, game is invalid or game uses an unsupported method of " | ||
| 184 | "crypto. Please double-check your keys"); | ||
| 179 | return -1; | 185 | return -1; |
| 180 | case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: | 186 | case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: |
| 181 | LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); | 187 | LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); |