mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu.git
				synced 2025-05-12 00:45:25 +00:00 
			
		
		
		
	Merge pull request #849 from DarkLordZach/xci
XCI and Encrypted NCA Support
This commit is contained in:
		
						commit
						2b06301dbf
					
				
							
								
								
									
										7
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @ -25,6 +25,9 @@ | |||||||
| [submodule "unicorn"] | [submodule "unicorn"] | ||||||
|     path = externals/unicorn |     path = externals/unicorn | ||||||
|     url = https://github.com/yuzu-emu/unicorn |     url = https://github.com/yuzu-emu/unicorn | ||||||
|  | [submodule "mbedtls"] | ||||||
|  |     path = externals/mbedtls | ||||||
|  |     url = https://github.com/DarkLordZach/mbedtls | ||||||
| [submodule "opus"] | [submodule "opus"] | ||||||
| 	path = externals/opus |     path = externals/opus | ||||||
| 	url = https://github.com/ogniK5377/opus.git |     url = https://github.com/ogniK5377/opus.git | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								externals/CMakeLists.txt
									
									
									
									
										vendored
									
									
								
							| @ -35,6 +35,10 @@ set(LZ4_BUNDLED_MODE ON) | |||||||
| add_subdirectory(lz4/contrib/cmake_unofficial) | add_subdirectory(lz4/contrib/cmake_unofficial) | ||||||
| target_include_directories(lz4_static INTERFACE ./lz4/lib) | target_include_directories(lz4_static INTERFACE ./lz4/lib) | ||||||
| 
 | 
 | ||||||
|  | # mbedtls | ||||||
|  | add_subdirectory(mbedtls) | ||||||
|  | target_include_directories(mbedtls PUBLIC ./mbedtls/include) | ||||||
|  | 
 | ||||||
| # MicroProfile | # MicroProfile | ||||||
| add_library(microprofile INTERFACE) | add_library(microprofile INTERFACE) | ||||||
| target_include_directories(microprofile INTERFACE ./microprofile) | target_include_directories(microprofile INTERFACE ./microprofile) | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								externals/mbedtls
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										1
									
								
								externals/mbedtls
									
									
									
									
										vendored
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | |||||||
|  | Subproject commit 06442b840ea62e2ceda0dc58f255cfff49fed5b6 | ||||||
| @ -32,6 +32,7 @@ | |||||||
| #define SDMC_DIR "sdmc" | #define SDMC_DIR "sdmc" | ||||||
| #define NAND_DIR "nand" | #define NAND_DIR "nand" | ||||||
| #define SYSDATA_DIR "sysdata" | #define SYSDATA_DIR "sysdata" | ||||||
|  | #define KEYS_DIR "keys" | ||||||
| #define LOG_DIR "log" | #define LOG_DIR "log" | ||||||
| 
 | 
 | ||||||
| // Filenames
 | // Filenames
 | ||||||
|  | |||||||
| @ -706,6 +706,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { | |||||||
|         paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); |         paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); | ||||||
|         paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); |         paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); | ||||||
|         paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); |         paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); | ||||||
|  |         paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); | ||||||
|         // TODO: Put the logs in a better location for each OS
 |         // TODO: Put the logs in a better location for each OS
 | ||||||
|         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); |         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); | ||||||
|     } |     } | ||||||
| @ -736,6 +737,19 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { | |||||||
|     return paths[path]; |     return paths[path]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::string GetHactoolConfigurationPath() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     PWSTR pw_local_path = nullptr; | ||||||
|  |     if (SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &pw_local_path) != S_OK) | ||||||
|  |         return ""; | ||||||
|  |     std::string local_path = Common::UTF16ToUTF8(pw_local_path); | ||||||
|  |     CoTaskMemFree(pw_local_path); | ||||||
|  |     return local_path + "\\.switch"; | ||||||
|  | #else | ||||||
|  |     return GetHomeDirectory() + "/.switch"; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
| size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { | size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { | ||||||
|     return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); |     return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ namespace FileUtil { | |||||||
| enum class UserPath { | enum class UserPath { | ||||||
|     CacheDir, |     CacheDir, | ||||||
|     ConfigDir, |     ConfigDir, | ||||||
|  |     KeysDir, | ||||||
|     LogDir, |     LogDir, | ||||||
|     NANDDir, |     NANDDir, | ||||||
|     RootDir, |     RootDir, | ||||||
| @ -125,6 +126,8 @@ bool SetCurrentDir(const std::string& directory); | |||||||
| // directory. To be used in "multi-user" mode (that is, installed).
 | // directory. To be used in "multi-user" mode (that is, installed).
 | ||||||
| const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); | const std::string& GetUserPath(UserPath path, const std::string& new_path = ""); | ||||||
| 
 | 
 | ||||||
|  | std::string GetHactoolConfigurationPath(); | ||||||
|  | 
 | ||||||
| // Returns the path to where the sys file are
 | // Returns the path to where the sys file are
 | ||||||
| std::string GetSysDirectory(); | std::string GetSysDirectory(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -217,6 +217,7 @@ void FileBackend::Write(const Entry& entry) { | |||||||
|     CLS(Input)                                                                                     \ |     CLS(Input)                                                                                     \ | ||||||
|     CLS(Network)                                                                                   \ |     CLS(Network)                                                                                   \ | ||||||
|     CLS(Loader)                                                                                    \ |     CLS(Loader)                                                                                    \ | ||||||
|  |     CLS(Crypto)                                                                                    \ | ||||||
|     CLS(WebService) |     CLS(WebService) | ||||||
| 
 | 
 | ||||||
| // GetClassName is a macro defined by Windows.h, grrr...
 | // GetClassName is a macro defined by Windows.h, grrr...
 | ||||||
|  | |||||||
| @ -102,6 +102,7 @@ enum class Class : ClassType { | |||||||
|     Audio_DSP,         ///< The HLE implementation of the DSP
 |     Audio_DSP,         ///< The HLE implementation of the DSP
 | ||||||
|     Audio_Sink,        ///< Emulator audio output backend
 |     Audio_Sink,        ///< Emulator audio output backend
 | ||||||
|     Loader,            ///< ROM loader
 |     Loader,            ///< ROM loader
 | ||||||
|  |     Crypto,            ///< Cryptographic engine/functions
 | ||||||
|     Input,             ///< Input emulation
 |     Input,             ///< Input emulation
 | ||||||
|     Network,           ///< Network emulation
 |     Network,           ///< Network emulation
 | ||||||
|     WebService,        ///< Interface to yuzu Web Services
 |     WebService,        ///< Interface to yuzu Web Services
 | ||||||
|  | |||||||
| @ -12,6 +12,16 @@ add_library(core STATIC | |||||||
|     core_timing.h |     core_timing.h | ||||||
|     core_timing_util.cpp |     core_timing_util.cpp | ||||||
|     core_timing_util.h |     core_timing_util.h | ||||||
|  |     crypto/aes_util.cpp | ||||||
|  |     crypto/aes_util.h | ||||||
|  |     crypto/encryption_layer.cpp | ||||||
|  |     crypto/encryption_layer.h | ||||||
|  |     crypto/key_manager.cpp | ||||||
|  |     crypto/key_manager.h | ||||||
|  |     crypto/ctr_encryption_layer.cpp | ||||||
|  |     crypto/ctr_encryption_layer.h | ||||||
|  |     file_sys/card_image.cpp | ||||||
|  |     file_sys/card_image.h | ||||||
|     file_sys/content_archive.cpp |     file_sys/content_archive.cpp | ||||||
|     file_sys/content_archive.h |     file_sys/content_archive.h | ||||||
|     file_sys/control_metadata.cpp |     file_sys/control_metadata.cpp | ||||||
| @ -327,6 +337,8 @@ add_library(core STATIC | |||||||
|     loader/nro.h |     loader/nro.h | ||||||
|     loader/nso.cpp |     loader/nso.cpp | ||||||
|     loader/nso.h |     loader/nso.h | ||||||
|  |     loader/xci.cpp | ||||||
|  |     loader/xci.h | ||||||
|     memory.cpp |     memory.cpp | ||||||
|     memory.h |     memory.h | ||||||
|     memory_hook.cpp |     memory_hook.cpp | ||||||
| @ -346,7 +358,7 @@ add_library(core STATIC | |||||||
| create_target_directory_groups(core) | create_target_directory_groups(core) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) | ||||||
| target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static opus unicorn) | target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn) | ||||||
| 
 | 
 | ||||||
| if (ARCHITECTURE_x86_64) | if (ARCHITECTURE_x86_64) | ||||||
|     target_sources(core PRIVATE |     target_sources(core PRIVATE | ||||||
|  | |||||||
| @ -99,8 +99,10 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||||||
|                      static_cast<int>(system_mode.second)); |                      static_cast<int>(system_mode.second)); | ||||||
| 
 | 
 | ||||||
|         switch (system_mode.second) { |         switch (system_mode.second) { | ||||||
|         case Loader::ResultStatus::ErrorEncrypted: |         case Loader::ResultStatus::ErrorMissingKeys: | ||||||
|             return ResultStatus::ErrorLoader_ErrorEncrypted; |             return ResultStatus::ErrorLoader_ErrorMissingKeys; | ||||||
|  |         case Loader::ResultStatus::ErrorDecrypting: | ||||||
|  |             return ResultStatus::ErrorLoader_ErrorDecrypting; | ||||||
|         case Loader::ResultStatus::ErrorInvalidFormat: |         case Loader::ResultStatus::ErrorInvalidFormat: | ||||||
|             return ResultStatus::ErrorLoader_ErrorInvalidFormat; |             return ResultStatus::ErrorLoader_ErrorInvalidFormat; | ||||||
|         case Loader::ResultStatus::ErrorUnsupportedArch: |         case Loader::ResultStatus::ErrorUnsupportedArch: | ||||||
| @ -124,8 +126,10 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file | |||||||
|         System::Shutdown(); |         System::Shutdown(); | ||||||
| 
 | 
 | ||||||
|         switch (load_result) { |         switch (load_result) { | ||||||
|         case Loader::ResultStatus::ErrorEncrypted: |         case Loader::ResultStatus::ErrorMissingKeys: | ||||||
|             return ResultStatus::ErrorLoader_ErrorEncrypted; |             return ResultStatus::ErrorLoader_ErrorMissingKeys; | ||||||
|  |         case Loader::ResultStatus::ErrorDecrypting: | ||||||
|  |             return ResultStatus::ErrorLoader_ErrorDecrypting; | ||||||
|         case Loader::ResultStatus::ErrorInvalidFormat: |         case Loader::ResultStatus::ErrorInvalidFormat: | ||||||
|             return ResultStatus::ErrorLoader_ErrorInvalidFormat; |             return ResultStatus::ErrorLoader_ErrorInvalidFormat; | ||||||
|         case Loader::ResultStatus::ErrorUnsupportedArch: |         case Loader::ResultStatus::ErrorUnsupportedArch: | ||||||
|  | |||||||
| @ -43,12 +43,14 @@ public: | |||||||
| 
 | 
 | ||||||
|     /// Enumeration representing the return values of the System Initialize and Load process.
 |     /// Enumeration representing the return values of the System Initialize and Load process.
 | ||||||
|     enum class ResultStatus : u32 { |     enum class ResultStatus : u32 { | ||||||
|         Success,                    ///< Succeeded
 |         Success,                      ///< Succeeded
 | ||||||
|         ErrorNotInitialized,        ///< Error trying to use core prior to initialization
 |         ErrorNotInitialized,          ///< Error trying to use core prior to initialization
 | ||||||
|         ErrorGetLoader,             ///< Error finding the correct application loader
 |         ErrorGetLoader,               ///< Error finding the correct application loader
 | ||||||
|         ErrorSystemMode,            ///< Error determining the system mode
 |         ErrorSystemMode,              ///< Error determining the system mode
 | ||||||
|         ErrorLoader,                ///< Error loading the specified application
 |         ErrorLoader,                  ///< Error loading the specified application
 | ||||||
|         ErrorLoader_ErrorEncrypted, ///< Error loading the specified application due to encryption
 |         ErrorLoader_ErrorMissingKeys, ///< Error because the key/keys needed to run could not be
 | ||||||
|  |                                       ///< found.
 | ||||||
|  |         ErrorLoader_ErrorDecrypting,  ///< Error loading the specified application due to encryption
 | ||||||
|         ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
 |         ErrorLoader_ErrorInvalidFormat, ///< Error loading the specified application due to an
 | ||||||
|                                         /// invalid format
 |                                         /// invalid format
 | ||||||
|         ErrorSystemFiles,               ///< Error in finding system files
 |         ErrorSystemFiles,               ///< Error in finding system files
 | ||||||
|  | |||||||
							
								
								
									
										112
									
								
								src/core/crypto/aes_util.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/core/crypto/aes_util.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <mbedtls/cipher.h> | ||||||
|  | #include "core/crypto/aes_util.h" | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR), | ||||||
|  |               "CTR has incorrect value."); | ||||||
|  | static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB), | ||||||
|  |               "ECB has incorrect value."); | ||||||
|  | static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS), | ||||||
|  |               "XTS has incorrect value."); | ||||||
|  | 
 | ||||||
|  | // Structure to hide mbedtls types from header file
 | ||||||
|  | struct CipherContext { | ||||||
|  |     mbedtls_cipher_context_t encryption_context; | ||||||
|  |     mbedtls_cipher_context_t decryption_context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) | ||||||
|  |     : ctx(std::make_unique<CipherContext>()) { | ||||||
|  |     mbedtls_cipher_init(&ctx->encryption_context); | ||||||
|  |     mbedtls_cipher_init(&ctx->decryption_context); | ||||||
|  | 
 | ||||||
|  |     ASSERT_MSG((mbedtls_cipher_setup( | ||||||
|  |                     &ctx->encryption_context, | ||||||
|  |                     mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode))) || | ||||||
|  |                 mbedtls_cipher_setup( | ||||||
|  |                     &ctx->decryption_context, | ||||||
|  |                     mbedtls_cipher_info_from_type(static_cast<mbedtls_cipher_type_t>(mode)))) == 0, | ||||||
|  |                "Failed to initialize mbedtls ciphers."); | ||||||
|  | 
 | ||||||
|  |     ASSERT( | ||||||
|  |         !mbedtls_cipher_setkey(&ctx->encryption_context, key.data(), KeySize * 8, MBEDTLS_ENCRYPT)); | ||||||
|  |     ASSERT( | ||||||
|  |         !mbedtls_cipher_setkey(&ctx->decryption_context, key.data(), KeySize * 8, MBEDTLS_DECRYPT)); | ||||||
|  |     //"Failed to set key on mbedtls ciphers.");
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | AESCipher<Key, KeySize>::~AESCipher() { | ||||||
|  |     mbedtls_cipher_free(&ctx->encryption_context); | ||||||
|  |     mbedtls_cipher_free(&ctx->decryption_context); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) { | ||||||
|  |     ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) || | ||||||
|  |                 mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0, | ||||||
|  |                "Failed to set IV on mbedtls ciphers."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) { | ||||||
|  |     size_t written = 0; | ||||||
|  | 
 | ||||||
|  |     const auto context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context; | ||||||
|  | 
 | ||||||
|  |     mbedtls_cipher_reset(context); | ||||||
|  | 
 | ||||||
|  |     if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { | ||||||
|  |         mbedtls_cipher_update(context, src, size, dest, &written); | ||||||
|  |         if (written != size) | ||||||
|  |             LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", | ||||||
|  |                         size, written); | ||||||
|  |     } else { | ||||||
|  |         const auto block_size = mbedtls_cipher_get_block_size(context); | ||||||
|  | 
 | ||||||
|  |         for (size_t offset = 0; offset < size; offset += block_size) { | ||||||
|  |             auto length = std::min<size_t>(block_size, size - offset); | ||||||
|  |             mbedtls_cipher_update(context, src + offset, length, dest + offset, &written); | ||||||
|  |             if (written != length) | ||||||
|  |                 LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", | ||||||
|  |                             length, written); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     mbedtls_cipher_finish(context, nullptr, nullptr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, | ||||||
|  |                                            size_t sector_size, Op op) { | ||||||
|  |     if (size % sector_size > 0) { | ||||||
|  |         LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size."); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (size_t i = 0; i < size; i += sector_size) { | ||||||
|  |         SetIV(CalculateNintendoTweak(sector_id++)); | ||||||
|  |         Transcode<u8, u8>(src + i, sector_size, dest + i, op); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize> | ||||||
|  | std::vector<u8> AESCipher<Key, KeySize>::CalculateNintendoTweak(size_t sector_id) { | ||||||
|  |     std::vector<u8> out(0x10); | ||||||
|  |     for (size_t i = 0xF; i <= 0xF; --i) { | ||||||
|  |         out[i] = sector_id & 0xFF; | ||||||
|  |         sector_id >>= 8; | ||||||
|  |     } | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template class AESCipher<Key128>; | ||||||
|  | template class AESCipher<Key256>; | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										62
									
								
								src/core/crypto/aes_util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/core/crypto/aes_util.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <vector> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | struct CipherContext; | ||||||
|  | 
 | ||||||
|  | enum class Mode { | ||||||
|  |     CTR = 11, | ||||||
|  |     ECB = 2, | ||||||
|  |     XTS = 70, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class Op { | ||||||
|  |     Encrypt, | ||||||
|  |     Decrypt, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename Key, size_t KeySize = sizeof(Key)> | ||||||
|  | class AESCipher { | ||||||
|  |     static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8."); | ||||||
|  |     static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); | ||||||
|  | 
 | ||||||
|  | public: | ||||||
|  |     AESCipher(Key key, Mode mode); | ||||||
|  | 
 | ||||||
|  |     ~AESCipher(); | ||||||
|  | 
 | ||||||
|  |     void SetIV(std::vector<u8> iv); | ||||||
|  | 
 | ||||||
|  |     template <typename Source, typename Dest> | ||||||
|  |     void Transcode(const Source* src, size_t size, Dest* dest, Op op) { | ||||||
|  |         Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Transcode(const u8* src, size_t size, u8* dest, Op op); | ||||||
|  | 
 | ||||||
|  |     template <typename Source, typename Dest> | ||||||
|  |     void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id, | ||||||
|  |                       size_t sector_size, Op op) { | ||||||
|  |         XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id, | ||||||
|  |                      sector_size, op); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, | ||||||
|  |                       Op op); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::unique_ptr<CipherContext> ctx; | ||||||
|  | 
 | ||||||
|  |     static std::vector<u8> CalculateNintendoTweak(size_t sector_id); | ||||||
|  | }; | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										56
									
								
								src/core/crypto/ctr_encryption_layer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/core/crypto/ctr_encryption_layer.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <cstring> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "core/crypto/ctr_encryption_layer.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) | ||||||
|  |     : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), | ||||||
|  |       iv(16, 0) {} | ||||||
|  | 
 | ||||||
|  | size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { | ||||||
|  |     if (length == 0) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     const auto sector_offset = offset & 0xF; | ||||||
|  |     if (sector_offset == 0) { | ||||||
|  |         UpdateIV(base_offset + offset); | ||||||
|  |         std::vector<u8> raw = base->ReadBytes(length, offset); | ||||||
|  |         if (raw.size() != length) | ||||||
|  |             return Read(data, raw.size(), offset); | ||||||
|  |         cipher.Transcode(raw.data(), length, data, Op::Decrypt); | ||||||
|  |         return length; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // offset does not fall on block boundary (0x10)
 | ||||||
|  |     std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset); | ||||||
|  |     UpdateIV(base_offset + offset - sector_offset); | ||||||
|  |     cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt); | ||||||
|  |     size_t read = 0x10 - sector_offset; | ||||||
|  | 
 | ||||||
|  |     if (length + sector_offset < 0x10) { | ||||||
|  |         std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); | ||||||
|  |         return read; | ||||||
|  |     } | ||||||
|  |     std::memcpy(data, block.data() + sector_offset, read); | ||||||
|  |     return read + Read(data + read, length - read, offset + read); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) { | ||||||
|  |     const auto length = std::min(iv_.size(), iv.size()); | ||||||
|  |     iv.assign(iv_.cbegin(), iv_.cbegin() + length); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CTREncryptionLayer::UpdateIV(size_t offset) const { | ||||||
|  |     offset >>= 4; | ||||||
|  |     for (size_t i = 0; i < 8; ++i) { | ||||||
|  |         iv[16 - i - 1] = offset & 0xFF; | ||||||
|  |         offset >>= 8; | ||||||
|  |     } | ||||||
|  |     cipher.SetIV(iv); | ||||||
|  | } | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										33
									
								
								src/core/crypto/ctr_encryption_layer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/crypto/ctr_encryption_layer.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | #include "core/crypto/aes_util.h" | ||||||
|  | #include "core/crypto/encryption_layer.h" | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | // Sits on top of a VirtualFile and provides CTR-mode AES decription.
 | ||||||
|  | class CTREncryptionLayer : public EncryptionLayer { | ||||||
|  | public: | ||||||
|  |     CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); | ||||||
|  | 
 | ||||||
|  |     size_t Read(u8* data, size_t length, size_t offset) const override; | ||||||
|  | 
 | ||||||
|  |     void SetIV(const std::vector<u8>& iv); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     size_t base_offset; | ||||||
|  | 
 | ||||||
|  |     // Must be mutable as operations modify cipher contexts.
 | ||||||
|  |     mutable AESCipher<Key128> cipher; | ||||||
|  |     mutable std::vector<u8> iv; | ||||||
|  | 
 | ||||||
|  |     void UpdateIV(size_t offset) const; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										42
									
								
								src/core/crypto/encryption_layer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/core/crypto/encryption_layer.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include "core/crypto/encryption_layer.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | EncryptionLayer::EncryptionLayer(FileSys::VirtualFile base_) : base(std::move(base_)) {} | ||||||
|  | 
 | ||||||
|  | std::string EncryptionLayer::GetName() const { | ||||||
|  |     return base->GetName(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t EncryptionLayer::GetSize() const { | ||||||
|  |     return base->GetSize(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool EncryptionLayer::Resize(size_t new_size) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<FileSys::VfsDirectory> EncryptionLayer::GetContainingDirectory() const { | ||||||
|  |     return base->GetContainingDirectory(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool EncryptionLayer::IsWritable() const { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool EncryptionLayer::IsReadable() const { | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool EncryptionLayer::Rename(std::string_view name) { | ||||||
|  |     return base->Rename(name); | ||||||
|  | } | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										32
									
								
								src/core/crypto/encryption_layer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/core/crypto/encryption_layer.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | // Basically non-functional class that implements all of the methods that are irrelevant to an
 | ||||||
|  | // EncryptionLayer. Reduces duplicate code.
 | ||||||
|  | class EncryptionLayer : public FileSys::VfsFile { | ||||||
|  | public: | ||||||
|  |     explicit EncryptionLayer(FileSys::VirtualFile base); | ||||||
|  | 
 | ||||||
|  |     size_t Read(u8* data, size_t length, size_t offset) const override = 0; | ||||||
|  | 
 | ||||||
|  |     std::string GetName() const override; | ||||||
|  |     size_t GetSize() const override; | ||||||
|  |     bool Resize(size_t new_size) override; | ||||||
|  |     std::shared_ptr<FileSys::VfsDirectory> GetContainingDirectory() const override; | ||||||
|  |     bool IsWritable() const override; | ||||||
|  |     bool IsReadable() const override; | ||||||
|  |     size_t Write(const u8* data, size_t length, size_t offset) override; | ||||||
|  |     bool Rename(std::string_view name) override; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     FileSys::VirtualFile base; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										215
									
								
								src/core/crypto/key_manager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								src/core/crypto/key_manager.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <fstream> | ||||||
|  | #include <locale> | ||||||
|  | #include <sstream> | ||||||
|  | #include <string_view> | ||||||
|  | #include <mbedtls/sha256.h> | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "common/common_paths.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "key_manager.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | static u8 ToHexNibble(char c1) { | ||||||
|  |     if (c1 >= 65 && c1 <= 70) | ||||||
|  |         return c1 - 55; | ||||||
|  |     if (c1 >= 97 && c1 <= 102) | ||||||
|  |         return c1 - 87; | ||||||
|  |     if (c1 >= 48 && c1 <= 57) | ||||||
|  |         return c1 - 48; | ||||||
|  |     throw std::logic_error("Invalid hex digit"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <size_t Size> | ||||||
|  | static std::array<u8, Size> HexStringToArray(std::string_view str) { | ||||||
|  |     std::array<u8, Size> out{}; | ||||||
|  |     for (size_t i = 0; i < 2 * Size; i += 2) { | ||||||
|  |         auto d1 = str[i]; | ||||||
|  |         auto d2 = str[i + 1]; | ||||||
|  |         out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2); | ||||||
|  |     } | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::array<u8, 16> operator""_array16(const char* str, size_t len) { | ||||||
|  |     if (len != 32) | ||||||
|  |         throw std::logic_error("Not of correct size."); | ||||||
|  |     return HexStringToArray<16>(str); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::array<u8, 32> operator""_array32(const char* str, size_t len) { | ||||||
|  |     if (len != 64) | ||||||
|  |         throw std::logic_error("Not of correct size."); | ||||||
|  |     return HexStringToArray<32>(str); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | KeyManager::KeyManager() { | ||||||
|  |     // Initialize keys
 | ||||||
|  |     const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); | ||||||
|  |     const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); | ||||||
|  |     if (Settings::values.use_dev_keys) { | ||||||
|  |         dev_mode = true; | ||||||
|  |         AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false); | ||||||
|  |     } else { | ||||||
|  |         dev_mode = false; | ||||||
|  |         AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void KeyManager::LoadFromFile(std::string_view filename_, bool is_title_keys) { | ||||||
|  |     const auto filename = std::string(filename_); | ||||||
|  |     std::ifstream file(filename); | ||||||
|  |     if (!file.is_open()) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     std::string line; | ||||||
|  |     while (std::getline(file, line)) { | ||||||
|  |         std::vector<std::string> out; | ||||||
|  |         std::stringstream stream(line); | ||||||
|  |         std::string item; | ||||||
|  |         while (std::getline(stream, item, '=')) | ||||||
|  |             out.push_back(std::move(item)); | ||||||
|  | 
 | ||||||
|  |         if (out.size() != 2) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); | ||||||
|  |         out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); | ||||||
|  | 
 | ||||||
|  |         if (is_title_keys) { | ||||||
|  |             auto rights_id_raw = HexStringToArray<16>(out[0]); | ||||||
|  |             u128 rights_id{}; | ||||||
|  |             std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); | ||||||
|  |             Key128 key = HexStringToArray<16>(out[1]); | ||||||
|  |             SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||||||
|  |         } else { | ||||||
|  |             std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower); | ||||||
|  |             if (s128_file_id.find(out[0]) != s128_file_id.end()) { | ||||||
|  |                 const auto index = s128_file_id.at(out[0]); | ||||||
|  |                 Key128 key = HexStringToArray<16>(out[1]); | ||||||
|  |                 SetKey(index.type, key, index.field1, index.field2); | ||||||
|  |             } else if (s256_file_id.find(out[0]) != s256_file_id.end()) { | ||||||
|  |                 const auto index = s256_file_id.at(out[0]); | ||||||
|  |                 Key256 key = HexStringToArray<32>(out[1]); | ||||||
|  |                 SetKey(index.type, key, index.field1, index.field2); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void KeyManager::AttemptLoadKeyFile(std::string_view dir1_, std::string_view dir2_, | ||||||
|  |                                     std::string_view filename_, bool title) { | ||||||
|  |     const std::string dir1(dir1_); | ||||||
|  |     const std::string dir2(dir2_); | ||||||
|  |     const std::string filename(filename_); | ||||||
|  |     if (FileUtil::Exists(dir1 + DIR_SEP + filename)) | ||||||
|  |         LoadFromFile(dir1 + DIR_SEP + filename, title); | ||||||
|  |     else if (FileUtil::Exists(dir2 + DIR_SEP + filename)) | ||||||
|  |         LoadFromFile(dir2 + DIR_SEP + filename, title); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const { | ||||||
|  |     return s128_keys.find({id, field1, field2}) != s128_keys.end(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool KeyManager::HasKey(S256KeyType id, u64 field1, u64 field2) const { | ||||||
|  |     return s256_keys.find({id, field1, field2}) != s256_keys.end(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Key128 KeyManager::GetKey(S128KeyType id, u64 field1, u64 field2) const { | ||||||
|  |     if (!HasKey(id, field1, field2)) | ||||||
|  |         return {}; | ||||||
|  |     return s128_keys.at({id, field1, field2}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { | ||||||
|  |     if (!HasKey(id, field1, field2)) | ||||||
|  |         return {}; | ||||||
|  |     return s256_keys.at({id, field1, field2}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { | ||||||
|  |     s128_keys[{id, field1, field2}] = key; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { | ||||||
|  |     s256_keys[{id, field1, field2}] = key; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool KeyManager::KeyFileExists(bool title) { | ||||||
|  |     const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); | ||||||
|  |     const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); | ||||||
|  |     if (title) { | ||||||
|  |         return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "title.keys") || | ||||||
|  |                FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "title.keys"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (Settings::values.use_dev_keys) { | ||||||
|  |         return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "dev.keys") || | ||||||
|  |                FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "dev.keys"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return FileUtil::Exists(hactool_keys_dir + DIR_SEP + "prod.keys") || | ||||||
|  |            FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { | ||||||
|  |     {"master_key_00", {S128KeyType::Master, 0, 0}}, | ||||||
|  |     {"master_key_01", {S128KeyType::Master, 1, 0}}, | ||||||
|  |     {"master_key_02", {S128KeyType::Master, 2, 0}}, | ||||||
|  |     {"master_key_03", {S128KeyType::Master, 3, 0}}, | ||||||
|  |     {"master_key_04", {S128KeyType::Master, 4, 0}}, | ||||||
|  |     {"package1_key_00", {S128KeyType::Package1, 0, 0}}, | ||||||
|  |     {"package1_key_01", {S128KeyType::Package1, 1, 0}}, | ||||||
|  |     {"package1_key_02", {S128KeyType::Package1, 2, 0}}, | ||||||
|  |     {"package1_key_03", {S128KeyType::Package1, 3, 0}}, | ||||||
|  |     {"package1_key_04", {S128KeyType::Package1, 4, 0}}, | ||||||
|  |     {"package2_key_00", {S128KeyType::Package2, 0, 0}}, | ||||||
|  |     {"package2_key_01", {S128KeyType::Package2, 1, 0}}, | ||||||
|  |     {"package2_key_02", {S128KeyType::Package2, 2, 0}}, | ||||||
|  |     {"package2_key_03", {S128KeyType::Package2, 3, 0}}, | ||||||
|  |     {"package2_key_04", {S128KeyType::Package2, 4, 0}}, | ||||||
|  |     {"titlekek_00", {S128KeyType::Titlekek, 0, 0}}, | ||||||
|  |     {"titlekek_01", {S128KeyType::Titlekek, 1, 0}}, | ||||||
|  |     {"titlekek_02", {S128KeyType::Titlekek, 2, 0}}, | ||||||
|  |     {"titlekek_03", {S128KeyType::Titlekek, 3, 0}}, | ||||||
|  |     {"titlekek_04", {S128KeyType::Titlekek, 4, 0}}, | ||||||
|  |     {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, | ||||||
|  |     {"key_area_key_application_00", | ||||||
|  |      {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||||||
|  |     {"key_area_key_application_01", | ||||||
|  |      {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||||||
|  |     {"key_area_key_application_02", | ||||||
|  |      {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||||||
|  |     {"key_area_key_application_03", | ||||||
|  |      {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||||||
|  |     {"key_area_key_application_04", | ||||||
|  |      {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}}, | ||||||
|  |     {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||||||
|  |     {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||||||
|  |     {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||||||
|  |     {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||||||
|  |     {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}}, | ||||||
|  |     {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}}, | ||||||
|  |     {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}}, | ||||||
|  |     {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}}, | ||||||
|  |     {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}}, | ||||||
|  |     {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = { | ||||||
|  |     {"header_key", {S256KeyType::Header, 0, 0}}, | ||||||
|  |     {"sd_card_save_key", {S256KeyType::SDSave, 0, 0}}, | ||||||
|  |     {"sd_card_nca_key", {S256KeyType::SDNCA, 0, 0}}, | ||||||
|  | }; | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										119
									
								
								src/core/crypto/key_manager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/core/crypto/key_manager.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include <vector> | ||||||
|  | #include <fmt/format.h> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | using Key128 = std::array<u8, 0x10>; | ||||||
|  | using Key256 = std::array<u8, 0x20>; | ||||||
|  | using SHA256Hash = std::array<u8, 0x20>; | ||||||
|  | 
 | ||||||
|  | static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); | ||||||
|  | static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big."); | ||||||
|  | 
 | ||||||
|  | enum class S256KeyType : u64 { | ||||||
|  |     Header, //
 | ||||||
|  |     SDSave, //
 | ||||||
|  |     SDNCA,  //
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class S128KeyType : u64 { | ||||||
|  |     Master,        // f1=crypto revision
 | ||||||
|  |     Package1,      // f1=crypto revision
 | ||||||
|  |     Package2,      // f1=crypto revision
 | ||||||
|  |     Titlekek,      // f1=crypto revision
 | ||||||
|  |     ETicketRSAKek, //
 | ||||||
|  |     KeyArea,       // f1=crypto revision f2=type {app, ocean, system}
 | ||||||
|  |     SDSeed,        //
 | ||||||
|  |     Titlekey,      // f1=rights id LSB f2=rights id MSB
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum class KeyAreaKeyType : u8 { | ||||||
|  |     Application, | ||||||
|  |     Ocean, | ||||||
|  |     System, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename KeyType> | ||||||
|  | struct KeyIndex { | ||||||
|  |     KeyType type; | ||||||
|  |     u64 field1; | ||||||
|  |     u64 field2; | ||||||
|  | 
 | ||||||
|  |     std::string DebugInfo() const { | ||||||
|  |         u8 key_size = 16; | ||||||
|  |         if constexpr (std::is_same_v<KeyType, S256KeyType>) | ||||||
|  |             key_size = 32; | ||||||
|  |         return fmt::format("key_size={:02X}, key={:02X}, field1={:016X}, field2={:016X}", key_size, | ||||||
|  |                            static_cast<u8>(type), field1, field2); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // The following two (== and hash) are so KeyIndex can be a key in unordered_map
 | ||||||
|  | 
 | ||||||
|  | template <typename KeyType> | ||||||
|  | bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { | ||||||
|  |     return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename KeyType> | ||||||
|  | bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) { | ||||||
|  |     return !operator==(lhs, rhs); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Core::Crypto
 | ||||||
|  | 
 | ||||||
|  | namespace std { | ||||||
|  | template <typename KeyType> | ||||||
|  | struct hash<Core::Crypto::KeyIndex<KeyType>> { | ||||||
|  |     size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const { | ||||||
|  |         using std::hash; | ||||||
|  | 
 | ||||||
|  |         return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^ | ||||||
|  |                (hash<u64>()(k.field2) << 1); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | } // namespace std
 | ||||||
|  | 
 | ||||||
|  | namespace Core::Crypto { | ||||||
|  | 
 | ||||||
|  | std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); | ||||||
|  | std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); | ||||||
|  | 
 | ||||||
|  | class KeyManager { | ||||||
|  | public: | ||||||
|  |     KeyManager(); | ||||||
|  | 
 | ||||||
|  |     bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||||||
|  |     bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||||||
|  | 
 | ||||||
|  |     Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||||||
|  |     Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const; | ||||||
|  | 
 | ||||||
|  |     void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); | ||||||
|  |     void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); | ||||||
|  | 
 | ||||||
|  |     static bool KeyFileExists(bool title); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys; | ||||||
|  |     std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys; | ||||||
|  | 
 | ||||||
|  |     bool dev_mode; | ||||||
|  |     void LoadFromFile(std::string_view filename, bool is_title_keys); | ||||||
|  |     void AttemptLoadKeyFile(std::string_view dir1, std::string_view dir2, std::string_view filename, | ||||||
|  |                             bool title); | ||||||
|  | 
 | ||||||
|  |     static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id; | ||||||
|  |     static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id; | ||||||
|  | }; | ||||||
|  | } // namespace Core::Crypto
 | ||||||
							
								
								
									
										5
									
								
								src/core/crypto/sha_util.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/core/crypto/sha_util.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | namespace Crypto {} // namespace Crypto
 | ||||||
							
								
								
									
										20
									
								
								src/core/crypto/sha_util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/core/crypto/sha_util.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | #include "key_manager.h" | ||||||
|  | #include "mbedtls/cipher.h" | ||||||
|  | 
 | ||||||
|  | namespace Crypto { | ||||||
|  | typedef std::array<u8, 0x20> SHA256Hash; | ||||||
|  | 
 | ||||||
|  | inline SHA256Hash operator"" _HASH(const char* data, size_t len) { | ||||||
|  |     if (len != 0x40) | ||||||
|  |         return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Crypto
 | ||||||
							
								
								
									
										149
									
								
								src/core/file_sys/card_image.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/core/file_sys/card_image.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <string> | ||||||
|  | #include <core/loader/loader.h> | ||||||
|  | #include "core/file_sys/card_image.h" | ||||||
|  | #include "core/file_sys/partition_filesystem.h" | ||||||
|  | #include "core/file_sys/vfs_offset.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { | ||||||
|  |     if (file->ReadObject(&header) != sizeof(GamecardHeader)) { | ||||||
|  |         status = Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { | ||||||
|  |         status = Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     PartitionFilesystem main_hfs( | ||||||
|  |         std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset)); | ||||||
|  | 
 | ||||||
|  |     if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { | ||||||
|  |         status = main_hfs.GetStatus(); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", | ||||||
|  |                                                                      "logo"}; | ||||||
|  | 
 | ||||||
|  |     for (XCIPartition partition : | ||||||
|  |          {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { | ||||||
|  |         auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); | ||||||
|  |         if (raw != nullptr) | ||||||
|  |             partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto result = AddNCAFromPartition(XCIPartition::Secure); | ||||||
|  |     if (result != Loader::ResultStatus::Success) { | ||||||
|  |         status = result; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     result = AddNCAFromPartition(XCIPartition::Update); | ||||||
|  |     if (result != Loader::ResultStatus::Success) { | ||||||
|  |         status = result; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     result = AddNCAFromPartition(XCIPartition::Normal); | ||||||
|  |     if (result != Loader::ResultStatus::Success) { | ||||||
|  |         status = result; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (GetFormatVersion() >= 0x2) { | ||||||
|  |         result = AddNCAFromPartition(XCIPartition::Logo); | ||||||
|  |         if (result != Loader::ResultStatus::Success) { | ||||||
|  |             status = result; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     status = Loader::ResultStatus::Success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Loader::ResultStatus XCI::GetStatus() const { | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir XCI::GetPartition(XCIPartition partition) const { | ||||||
|  |     return partitions[static_cast<size_t>(partition)]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir XCI::GetSecurePartition() const { | ||||||
|  |     return GetPartition(XCIPartition::Secure); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir XCI::GetNormalPartition() const { | ||||||
|  |     return GetPartition(XCIPartition::Normal); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir XCI::GetUpdatePartition() const { | ||||||
|  |     return GetPartition(XCIPartition::Update); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualDir XCI::GetLogoPartition() const { | ||||||
|  |     return GetPartition(XCIPartition::Logo); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { | ||||||
|  |     const auto iter = | ||||||
|  |         std::find_if(ncas.begin(), ncas.end(), | ||||||
|  |                      [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; }); | ||||||
|  |     return iter == ncas.end() ? nullptr : *iter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { | ||||||
|  |     auto nca = GetNCAByType(type); | ||||||
|  |     if (nca != nullptr) | ||||||
|  |         return nca->GetBaseFile(); | ||||||
|  |     return nullptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const { | ||||||
|  |     return {}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const { | ||||||
|  |     return std::vector<std::shared_ptr<VfsDirectory>>(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string XCI::GetName() const { | ||||||
|  |     return file->GetName(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const { | ||||||
|  |     return file->GetContainingDirectory(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { | ||||||
|  |     if (partitions[static_cast<size_t>(part)] == nullptr) { | ||||||
|  |         return Loader::ResultStatus::ErrorInvalidFormat; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { | ||||||
|  |         if (file->GetExtension() != "nca") | ||||||
|  |             continue; | ||||||
|  |         auto nca = std::make_shared<NCA>(file); | ||||||
|  |         if (nca->GetStatus() == Loader::ResultStatus::Success) | ||||||
|  |             ncas.push_back(std::move(nca)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return Loader::ResultStatus::Success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | u8 XCI::GetFormatVersion() const { | ||||||
|  |     return GetLogoPartition() == nullptr ? 0x1 : 0x2; | ||||||
|  | } | ||||||
|  | } // namespace FileSys
 | ||||||
							
								
								
									
										96
									
								
								src/core/file_sys/card_image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/core/file_sys/card_image.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <vector> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/swap.h" | ||||||
|  | #include "core/file_sys/content_archive.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
|  | 
 | ||||||
|  | namespace FileSys { | ||||||
|  | 
 | ||||||
|  | enum class GamecardSize : u8 { | ||||||
|  |     S_1GB = 0xFA, | ||||||
|  |     S_2GB = 0xF8, | ||||||
|  |     S_4GB = 0xF0, | ||||||
|  |     S_8GB = 0xE0, | ||||||
|  |     S_16GB = 0xE1, | ||||||
|  |     S_32GB = 0xE2, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct GamecardInfo { | ||||||
|  |     std::array<u8, 0x70> data; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); | ||||||
|  | 
 | ||||||
|  | struct GamecardHeader { | ||||||
|  |     std::array<u8, 0x100> signature; | ||||||
|  |     u32_le magic; | ||||||
|  |     u32_le secure_area_start; | ||||||
|  |     u32_le backup_area_start; | ||||||
|  |     u8 kek_index; | ||||||
|  |     GamecardSize size; | ||||||
|  |     u8 header_version; | ||||||
|  |     u8 flags; | ||||||
|  |     u64_le package_id; | ||||||
|  |     u64_le valid_data_end; | ||||||
|  |     u128 info_iv; | ||||||
|  |     u64_le hfs_offset; | ||||||
|  |     u64_le hfs_size; | ||||||
|  |     std::array<u8, 0x20> hfs_header_hash; | ||||||
|  |     std::array<u8, 0x20> initial_data_hash; | ||||||
|  |     u32_le secure_mode_flag; | ||||||
|  |     u32_le title_key_flag; | ||||||
|  |     u32_le key_flag; | ||||||
|  |     u32_le normal_area_end; | ||||||
|  |     GamecardInfo info; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); | ||||||
|  | 
 | ||||||
|  | enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; | ||||||
|  | 
 | ||||||
|  | class XCI : public ReadOnlyVfsDirectory { | ||||||
|  | public: | ||||||
|  |     explicit XCI(VirtualFile file); | ||||||
|  | 
 | ||||||
|  |     Loader::ResultStatus GetStatus() const; | ||||||
|  | 
 | ||||||
|  |     u8 GetFormatVersion() const; | ||||||
|  | 
 | ||||||
|  |     VirtualDir GetPartition(XCIPartition partition) const; | ||||||
|  |     VirtualDir GetSecurePartition() const; | ||||||
|  |     VirtualDir GetNormalPartition() const; | ||||||
|  |     VirtualDir GetUpdatePartition() const; | ||||||
|  |     VirtualDir GetLogoPartition() const; | ||||||
|  | 
 | ||||||
|  |     std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; | ||||||
|  |     VirtualFile GetNCAFileByType(NCAContentType type) const; | ||||||
|  | 
 | ||||||
|  |     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||||||
|  | 
 | ||||||
|  |     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||||||
|  | 
 | ||||||
|  |     std::string GetName() const override; | ||||||
|  | 
 | ||||||
|  |     std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||||||
|  | 
 | ||||||
|  | protected: | ||||||
|  |     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Loader::ResultStatus AddNCAFromPartition(XCIPartition part); | ||||||
|  | 
 | ||||||
|  |     VirtualFile file; | ||||||
|  |     GamecardHeader header{}; | ||||||
|  | 
 | ||||||
|  |     Loader::ResultStatus status; | ||||||
|  | 
 | ||||||
|  |     std::vector<VirtualDir> partitions; | ||||||
|  |     std::vector<std::shared_ptr<NCA>> ncas; | ||||||
|  | }; | ||||||
|  | } // namespace FileSys
 | ||||||
| @ -4,12 +4,14 @@ | |||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <utility> | #include <utility> | ||||||
| 
 | #include <boost/optional.hpp> | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | #include "core/crypto/aes_util.h" | ||||||
|  | #include "core/crypto/ctr_encryption_layer.h" | ||||||
| #include "core/file_sys/content_archive.h" | #include "core/file_sys/content_archive.h" | ||||||
|  | #include "core/file_sys/romfs.h" | ||||||
| #include "core/file_sys/vfs_offset.h" | #include "core/file_sys/vfs_offset.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "romfs.h" |  | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
| @ -29,11 +31,19 @@ enum class NCASectionFilesystemType : u8 { | |||||||
| struct NCASectionHeaderBlock { | struct NCASectionHeaderBlock { | ||||||
|     INSERT_PADDING_BYTES(3); |     INSERT_PADDING_BYTES(3); | ||||||
|     NCASectionFilesystemType filesystem_type; |     NCASectionFilesystemType filesystem_type; | ||||||
|     u8 crypto_type; |     NCASectionCryptoType crypto_type; | ||||||
|     INSERT_PADDING_BYTES(3); |     INSERT_PADDING_BYTES(3); | ||||||
| }; | }; | ||||||
| static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); | ||||||
| 
 | 
 | ||||||
|  | struct NCASectionRaw { | ||||||
|  |     NCASectionHeaderBlock header; | ||||||
|  |     std::array<u8, 0x138> block_data; | ||||||
|  |     std::array<u8, 0x8> section_ctr; | ||||||
|  |     INSERT_PADDING_BYTES(0xB8); | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); | ||||||
|  | 
 | ||||||
| struct PFS0Superblock { | struct PFS0Superblock { | ||||||
|     NCASectionHeaderBlock header_block; |     NCASectionHeaderBlock header_block; | ||||||
|     std::array<u8, 0x20> hash; |     std::array<u8, 0x20> hash; | ||||||
| @ -43,67 +53,170 @@ struct PFS0Superblock { | |||||||
|     u64_le hash_table_size; |     u64_le hash_table_size; | ||||||
|     u64_le pfs0_header_offset; |     u64_le pfs0_header_offset; | ||||||
|     u64_le pfs0_size; |     u64_le pfs0_size; | ||||||
|     INSERT_PADDING_BYTES(432); |     INSERT_PADDING_BYTES(0x1B0); | ||||||
| }; | }; | ||||||
| static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); | ||||||
| 
 | 
 | ||||||
| struct RomFSSuperblock { | struct RomFSSuperblock { | ||||||
|     NCASectionHeaderBlock header_block; |     NCASectionHeaderBlock header_block; | ||||||
|     IVFCHeader ivfc; |     IVFCHeader ivfc; | ||||||
|  |     INSERT_PADDING_BYTES(0x118); | ||||||
| }; | }; | ||||||
| static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | ||||||
|  | 
 | ||||||
|  | union NCASectionHeader { | ||||||
|  |     NCASectionRaw raw; | ||||||
|  |     PFS0Superblock pfs0; | ||||||
|  |     RomFSSuperblock romfs; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||||||
|  | 
 | ||||||
|  | bool IsValidNCA(const NCAHeader& header) { | ||||||
|  |     // TODO(DarkLordZach): Add NCA2/NCA0 support.
 | ||||||
|  |     return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { | ||||||
|  |     u8 master_key_id = header.crypto_type; | ||||||
|  |     if (header.crypto_type_2 > master_key_id) | ||||||
|  |         master_key_id = header.crypto_type_2; | ||||||
|  |     if (master_key_id > 0) | ||||||
|  |         --master_key_id; | ||||||
|  | 
 | ||||||
|  |     if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) | ||||||
|  |         return boost::none; | ||||||
|  | 
 | ||||||
|  |     std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); | ||||||
|  |     Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( | ||||||
|  |         keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), | ||||||
|  |         Core::Crypto::Mode::ECB); | ||||||
|  |     cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); | ||||||
|  | 
 | ||||||
|  |     Core::Crypto::Key128 out; | ||||||
|  |     if (type == NCASectionCryptoType::XTS) | ||||||
|  |         std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); | ||||||
|  |     else if (type == NCASectionCryptoType::CTR) | ||||||
|  |         std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); | ||||||
|  |     else | ||||||
|  |         LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", | ||||||
|  |                      static_cast<u8>(type)); | ||||||
|  |     u128 out_128{}; | ||||||
|  |     memcpy(out_128.data(), out.data(), 16); | ||||||
|  |     LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", | ||||||
|  |               master_key_id, header.key_index, out_128[1], out_128[0]); | ||||||
|  | 
 | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | VirtualFile NCA::Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const { | ||||||
|  |     if (!encrypted) | ||||||
|  |         return in; | ||||||
|  | 
 | ||||||
|  |     switch (header.raw.header.crypto_type) { | ||||||
|  |     case NCASectionCryptoType::NONE: | ||||||
|  |         LOG_DEBUG(Crypto, "called with mode=NONE"); | ||||||
|  |         return in; | ||||||
|  |     case NCASectionCryptoType::CTR: | ||||||
|  |         LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); | ||||||
|  |         { | ||||||
|  |             const auto key = GetKeyAreaKey(NCASectionCryptoType::CTR); | ||||||
|  |             if (key == boost::none) | ||||||
|  |                 return nullptr; | ||||||
|  |             auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( | ||||||
|  |                 std::move(in), key.value(), starting_offset); | ||||||
|  |             std::vector<u8> iv(16); | ||||||
|  |             for (u8 i = 0; i < 8; ++i) | ||||||
|  |                 iv[i] = header.raw.section_ctr[0x8 - i - 1]; | ||||||
|  |             out->SetIV(iv); | ||||||
|  |             return std::static_pointer_cast<VfsFile>(out); | ||||||
|  |         } | ||||||
|  |     case NCASectionCryptoType::XTS: | ||||||
|  |         // TODO(DarkLordZach): Implement XTSEncryptionLayer and title key encryption.
 | ||||||
|  |     default: | ||||||
|  |         LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", | ||||||
|  |                   static_cast<u8>(header.raw.header.crypto_type)); | ||||||
|  |         return nullptr; | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| NCA::NCA(VirtualFile file_) : file(std::move(file_)) { | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { | ||||||
|     if (sizeof(NCAHeader) != file->ReadObject(&header)) |     if (sizeof(NCAHeader) != file->ReadObject(&header)) | ||||||
|         LOG_CRITICAL(Loader, "File reader errored out during header read."); |         LOG_ERROR(Loader, "File reader errored out during header read."); | ||||||
|  | 
 | ||||||
|  |     encrypted = false; | ||||||
| 
 | 
 | ||||||
|     if (!IsValidNCA(header)) { |     if (!IsValidNCA(header)) { | ||||||
|         status = Loader::ResultStatus::ErrorInvalidFormat; |         NCAHeader dec_header{}; | ||||||
|         return; |         Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||||||
|  |             keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||||||
|  |         cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, | ||||||
|  |                             Core::Crypto::Op::Decrypt); | ||||||
|  |         if (IsValidNCA(dec_header)) { | ||||||
|  |             header = dec_header; | ||||||
|  |             encrypted = true; | ||||||
|  |         } else { | ||||||
|  |             if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) | ||||||
|  |                 status = Loader::ResultStatus::ErrorMissingKeys; | ||||||
|  |             else | ||||||
|  |                 status = Loader::ResultStatus::ErrorDecrypting; | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::ptrdiff_t number_sections = |     const std::ptrdiff_t number_sections = | ||||||
|         std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |         std::count_if(std::begin(header.section_tables), std::end(header.section_tables), | ||||||
|                       [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); |                       [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); | ||||||
| 
 | 
 | ||||||
|  |     std::vector<NCASectionHeader> sections(number_sections); | ||||||
|  |     const auto length_sections = SECTION_HEADER_SIZE * number_sections; | ||||||
|  | 
 | ||||||
|  |     if (encrypted) { | ||||||
|  |         auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); | ||||||
|  |         Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( | ||||||
|  |             keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); | ||||||
|  |         cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, | ||||||
|  |                             Core::Crypto::Op::Decrypt); | ||||||
|  |     } else { | ||||||
|  |         file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     for (std::ptrdiff_t i = 0; i < number_sections; ++i) { |     for (std::ptrdiff_t i = 0; i < number_sections; ++i) { | ||||||
|         // Seek to beginning of this section.
 |         auto section = sections[i]; | ||||||
|         NCASectionHeaderBlock block{}; |  | ||||||
|         if (sizeof(NCASectionHeaderBlock) != |  | ||||||
|             file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) |  | ||||||
|             LOG_CRITICAL(Loader, "File reader errored out during header read."); |  | ||||||
| 
 |  | ||||||
|         if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { |  | ||||||
|             RomFSSuperblock sb{}; |  | ||||||
|             if (sizeof(RomFSSuperblock) != |  | ||||||
|                 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) |  | ||||||
|                 LOG_CRITICAL(Loader, "File reader errored out during header read."); |  | ||||||
| 
 | 
 | ||||||
|  |         if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { | ||||||
|             const size_t romfs_offset = |             const size_t romfs_offset = | ||||||
|                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + |                 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + | ||||||
|                 sb.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; |                 section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; | ||||||
|             const size_t romfs_size = sb.ivfc.levels[IVFC_MAX_LEVEL - 1].size; |             const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; | ||||||
|             files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); |             auto dec = | ||||||
|             romfs = files.back(); |                 Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), | ||||||
|         } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { |                         romfs_offset); | ||||||
|             PFS0Superblock sb{}; |             if (dec != nullptr) { | ||||||
|             // Seek back to beginning of this section.
 |                 files.push_back(std::move(dec)); | ||||||
|             if (sizeof(PFS0Superblock) != |                 romfs = files.back(); | ||||||
|                 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) |             } else { | ||||||
|                 LOG_CRITICAL(Loader, "File reader errored out during header read."); |                 status = Loader::ResultStatus::ErrorMissingKeys; | ||||||
| 
 |                 return; | ||||||
|  |             } | ||||||
|  |         } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||||||
|             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * |             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * | ||||||
|                           MEDIA_OFFSET_MULTIPLIER) + |                           MEDIA_OFFSET_MULTIPLIER) + | ||||||
|                          sb.pfs0_header_offset; |                          section.pfs0.pfs0_header_offset; | ||||||
|             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - |             u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - | ||||||
|                                                   header.section_tables[i].media_offset); |                                                   header.section_tables[i].media_offset); | ||||||
|             auto npfs = std::make_shared<PartitionFilesystem>( |             auto dec = | ||||||
|                 std::make_shared<OffsetVfsFile>(file, size, offset)); |                 Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); | ||||||
|  |             if (dec != nullptr) { | ||||||
|  |                 auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); | ||||||
| 
 | 
 | ||||||
|             if (npfs->GetStatus() == Loader::ResultStatus::Success) { |                 if (npfs->GetStatus() == Loader::ResultStatus::Success) { | ||||||
|                 dirs.emplace_back(npfs); |                     dirs.push_back(std::move(npfs)); | ||||||
|                 if (IsDirectoryExeFS(dirs.back())) |                     if (IsDirectoryExeFS(dirs.back())) | ||||||
|                     exefs = dirs.back(); |                         exefs = dirs.back(); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 status = Loader::ResultStatus::ErrorMissingKeys; | ||||||
|  |                 return; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -153,6 +266,10 @@ VirtualDir NCA::GetExeFS() const { | |||||||
|     return exefs; |     return exefs; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | VirtualFile NCA::GetBaseFile() const { | ||||||
|  |     return file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,14 +8,18 @@ | |||||||
| #include <memory> | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
| 
 | #include <boost/optional.hpp> | ||||||
| #include "common/common_funcs.h" | #include "common/common_funcs.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
| #include "core/file_sys/partition_filesystem.h" | #include "core/file_sys/partition_filesystem.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
| 
 | 
 | ||||||
| namespace FileSys { | namespace FileSys { | ||||||
| 
 | 
 | ||||||
|  | union NCASectionHeader; | ||||||
|  | 
 | ||||||
| enum class NCAContentType : u8 { | enum class NCAContentType : u8 { | ||||||
|     Program = 0, |     Program = 0, | ||||||
|     Meta = 1, |     Meta = 1, | ||||||
| @ -24,6 +28,13 @@ enum class NCAContentType : u8 { | |||||||
|     Data = 4, |     Data = 4, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | enum class NCASectionCryptoType : u8 { | ||||||
|  |     NONE = 1, | ||||||
|  |     XTS = 2, | ||||||
|  |     CTR = 3, | ||||||
|  |     BKTR = 4, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| struct NCASectionTableEntry { | struct NCASectionTableEntry { | ||||||
|     u32_le media_offset; |     u32_le media_offset; | ||||||
|     u32_le media_end_offset; |     u32_le media_end_offset; | ||||||
| @ -48,7 +59,7 @@ struct NCAHeader { | |||||||
|     std::array<u8, 0x10> rights_id; |     std::array<u8, 0x10> rights_id; | ||||||
|     std::array<NCASectionTableEntry, 0x4> section_tables; |     std::array<NCASectionTableEntry, 0x4> section_tables; | ||||||
|     std::array<std::array<u8, 0x20>, 0x4> hash_tables; |     std::array<std::array<u8, 0x20>, 0x4> hash_tables; | ||||||
|     std::array<std::array<u8, 0x10>, 0x4> key_area; |     std::array<u8, 0x40> key_area; | ||||||
|     INSERT_PADDING_BYTES(0xC0); |     INSERT_PADDING_BYTES(0xC0); | ||||||
| }; | }; | ||||||
| static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); | ||||||
| @ -58,10 +69,7 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { | |||||||
|     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; |     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| inline bool IsValidNCA(const NCAHeader& header) { | bool IsValidNCA(const NCAHeader& header); | ||||||
|     return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || |  | ||||||
|            header.magic == Common::MakeMagic('N', 'C', 'A', '3'); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
 | // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
 | ||||||
| // After construction, use GetStatus to determine if the file is valid and ready to be used.
 | // After construction, use GetStatus to determine if the file is valid and ready to be used.
 | ||||||
| @ -81,10 +89,15 @@ public: | |||||||
|     VirtualFile GetRomFS() const; |     VirtualFile GetRomFS() const; | ||||||
|     VirtualDir GetExeFS() const; |     VirtualDir GetExeFS() const; | ||||||
| 
 | 
 | ||||||
|  |     VirtualFile GetBaseFile() const; | ||||||
|  | 
 | ||||||
| protected: | protected: | ||||||
|     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; |     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  |     boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||||||
|  |     VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; | ||||||
|  | 
 | ||||||
|     std::vector<VirtualDir> dirs; |     std::vector<VirtualDir> dirs; | ||||||
|     std::vector<VirtualFile> files; |     std::vector<VirtualFile> files; | ||||||
| 
 | 
 | ||||||
| @ -95,6 +108,10 @@ private: | |||||||
|     NCAHeader header{}; |     NCAHeader header{}; | ||||||
| 
 | 
 | ||||||
|     Loader::ResultStatus status{}; |     Loader::ResultStatus status{}; | ||||||
|  | 
 | ||||||
|  |     bool encrypted; | ||||||
|  | 
 | ||||||
|  |     Core::Crypto::KeyManager keys; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace FileSys
 | } // namespace FileSys
 | ||||||
|  | |||||||
| @ -285,6 +285,26 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { | ||||||
|  |     if (file1->GetSize() != file2->GetSize()) | ||||||
|  |         return false; | ||||||
|  | 
 | ||||||
|  |     std::vector<u8> f1_v(block_size); | ||||||
|  |     std::vector<u8> f2_v(block_size); | ||||||
|  |     for (size_t i = 0; i < file1->GetSize(); i += block_size) { | ||||||
|  |         auto f1_vs = file1->Read(f1_v.data(), block_size, i); | ||||||
|  |         auto f2_vs = file2->Read(f2_v.data(), block_size, i); | ||||||
|  | 
 | ||||||
|  |         if (f1_vs != f2_vs) | ||||||
|  |             return false; | ||||||
|  |         auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); | ||||||
|  |         if (iters.first != f1_v.end() && iters.second != f2_v.end()) | ||||||
|  |             return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | ||||||
|     if (src == nullptr || dest == nullptr) |     if (src == nullptr || dest == nullptr) | ||||||
|         return false; |         return false; | ||||||
|  | |||||||
| @ -245,6 +245,9 @@ struct ReadOnlyVfsDirectory : public VfsDirectory { | |||||||
|     bool Rename(std::string_view name) override; |     bool Rename(std::string_view name) override; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // Compare the two files, byte-for-byte, in increments specificed by block_size
 | ||||||
|  | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); | ||||||
|  | 
 | ||||||
| // A method that copies the raw data between two different implementations of VirtualFile. If you
 | // A method that copies the raw data between two different implementations of VirtualFile. If you
 | ||||||
| // are using the same implementation, it is probably better to use the Copy method in the parent
 | // are using the same implementation, it is probably better to use the Copy method in the parent
 | ||||||
| // directory of src/dest.
 | // directory of src/dest.
 | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ | |||||||
| #include "core/loader/nca.h" | #include "core/loader/nca.h" | ||||||
| #include "core/loader/nro.h" | #include "core/loader/nro.h" | ||||||
| #include "core/loader/nso.h" | #include "core/loader/nso.h" | ||||||
|  | #include "core/loader/xci.h" | ||||||
| 
 | 
 | ||||||
| namespace Loader { | namespace Loader { | ||||||
| 
 | 
 | ||||||
| @ -35,6 +36,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { | |||||||
|     CHECK_TYPE(NSO) |     CHECK_TYPE(NSO) | ||||||
|     CHECK_TYPE(NRO) |     CHECK_TYPE(NRO) | ||||||
|     CHECK_TYPE(NCA) |     CHECK_TYPE(NCA) | ||||||
|  |     CHECK_TYPE(XCI) | ||||||
| 
 | 
 | ||||||
| #undef CHECK_TYPE | #undef CHECK_TYPE | ||||||
| 
 | 
 | ||||||
| @ -60,6 +62,8 @@ FileType GuessFromFilename(const std::string& name) { | |||||||
|         return FileType::NSO; |         return FileType::NSO; | ||||||
|     if (extension == "nca") |     if (extension == "nca") | ||||||
|         return FileType::NCA; |         return FileType::NCA; | ||||||
|  |     if (extension == "xci") | ||||||
|  |         return FileType::XCI; | ||||||
| 
 | 
 | ||||||
|     return FileType::Unknown; |     return FileType::Unknown; | ||||||
| } | } | ||||||
| @ -74,6 +78,8 @@ const char* GetFileTypeString(FileType type) { | |||||||
|         return "NSO"; |         return "NSO"; | ||||||
|     case FileType::NCA: |     case FileType::NCA: | ||||||
|         return "NCA"; |         return "NCA"; | ||||||
|  |     case FileType::XCI: | ||||||
|  |         return "XCI"; | ||||||
|     case FileType::DeconstructedRomDirectory: |     case FileType::DeconstructedRomDirectory: | ||||||
|         return "Directory"; |         return "Directory"; | ||||||
|     case FileType::Error: |     case FileType::Error: | ||||||
| @ -111,6 +117,9 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT | |||||||
|     case FileType::NCA: |     case FileType::NCA: | ||||||
|         return std::make_unique<AppLoader_NCA>(std::move(file)); |         return std::make_unique<AppLoader_NCA>(std::move(file)); | ||||||
| 
 | 
 | ||||||
|  |     case FileType::XCI: | ||||||
|  |         return std::make_unique<AppLoader_XCI>(std::move(file)); | ||||||
|  | 
 | ||||||
|     // NX deconstructed ROM directory.
 |     // NX deconstructed ROM directory.
 | ||||||
|     case FileType::DeconstructedRomDirectory: |     case FileType::DeconstructedRomDirectory: | ||||||
|         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); |         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ enum class FileType { | |||||||
|     NSO, |     NSO, | ||||||
|     NRO, |     NRO, | ||||||
|     NCA, |     NCA, | ||||||
|  |     XCI, | ||||||
|     DeconstructedRomDirectory, |     DeconstructedRomDirectory, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -72,7 +73,8 @@ enum class ResultStatus { | |||||||
|     ErrorNotUsed, |     ErrorNotUsed, | ||||||
|     ErrorAlreadyLoaded, |     ErrorAlreadyLoaded, | ||||||
|     ErrorMemoryAllocationFailed, |     ErrorMemoryAllocationFailed, | ||||||
|     ErrorEncrypted, |     ErrorMissingKeys, | ||||||
|  |     ErrorDecrypting, | ||||||
|     ErrorUnsupportedArch, |     ErrorUnsupportedArch, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -25,12 +25,10 @@ namespace Loader { | |||||||
| AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file) : AppLoader(std::move(file)) {} | ||||||
| 
 | 
 | ||||||
| FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { | FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|     // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support.
 |     FileSys::NCA nca(file); | ||||||
|     FileSys::NCAHeader header{}; |  | ||||||
|     if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header)) |  | ||||||
|         return FileType::Error; |  | ||||||
| 
 | 
 | ||||||
|     if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) |     if (nca.GetStatus() == ResultStatus::Success && | ||||||
|  |         nca.GetType() == FileSys::NCAContentType::Program) | ||||||
|         return FileType::NCA; |         return FileType::NCA; | ||||||
| 
 | 
 | ||||||
|     return FileType::Error; |     return FileType::Error; | ||||||
| @ -98,12 +96,21 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | ||||||
|     if (nca == nullptr || nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) |     if (nca == nullptr) | ||||||
|  |         return ResultStatus::ErrorNotLoaded; | ||||||
|  |     if (nca->GetRomFS() == nullptr || nca->GetRomFS()->GetSize() == 0) | ||||||
|         return ResultStatus::ErrorNotUsed; |         return ResultStatus::ErrorNotUsed; | ||||||
|     dir = nca->GetRomFS(); |     dir = nca->GetRomFS(); | ||||||
|     return ResultStatus::Success; |     return ResultStatus::Success; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | ||||||
|  |     if (nca == nullptr) | ||||||
|  |         return ResultStatus::ErrorNotLoaded; | ||||||
|  |     out_program_id = nca->GetTitleId(); | ||||||
|  |     return ResultStatus::Success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| AppLoader_NCA::~AppLoader_NCA() = default; | AppLoader_NCA::~AppLoader_NCA() = default; | ||||||
| 
 | 
 | ||||||
| } // namespace Loader
 | } // namespace Loader
 | ||||||
|  | |||||||
| @ -33,6 +33,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||||||
| 
 | 
 | ||||||
|  |     ResultStatus ReadProgramId(u64& out_program_id) override; | ||||||
|  | 
 | ||||||
|     ~AppLoader_NCA(); |     ~AppLoader_NCA(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|  | |||||||
							
								
								
									
										74
									
								
								src/core/loader/xci.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/core/loader/xci.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  | #include "common/swap.h" | ||||||
|  | #include "core/core.h" | ||||||
|  | #include "core/file_sys/content_archive.h" | ||||||
|  | #include "core/file_sys/control_metadata.h" | ||||||
|  | #include "core/file_sys/program_metadata.h" | ||||||
|  | #include "core/file_sys/romfs.h" | ||||||
|  | #include "core/gdbstub/gdbstub.h" | ||||||
|  | #include "core/hle/kernel/process.h" | ||||||
|  | #include "core/hle/kernel/resource_limit.h" | ||||||
|  | #include "core/hle/service/filesystem/filesystem.h" | ||||||
|  | #include "core/loader/nso.h" | ||||||
|  | #include "core/loader/xci.h" | ||||||
|  | #include "core/memory.h" | ||||||
|  | 
 | ||||||
|  | namespace Loader { | ||||||
|  | 
 | ||||||
|  | AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) | ||||||
|  |     : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), | ||||||
|  |       nca_loader(std::make_unique<AppLoader_NCA>( | ||||||
|  |           xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {} | ||||||
|  | 
 | ||||||
|  | AppLoader_XCI::~AppLoader_XCI() = default; | ||||||
|  | 
 | ||||||
|  | FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) { | ||||||
|  |     FileSys::XCI xci(file); | ||||||
|  | 
 | ||||||
|  |     if (xci.GetStatus() == ResultStatus::Success && | ||||||
|  |         xci.GetNCAByType(FileSys::NCAContentType::Program) != nullptr && | ||||||
|  |         AppLoader_NCA::IdentifyType(xci.GetNCAFileByType(FileSys::NCAContentType::Program)) == | ||||||
|  |             FileType::NCA) { | ||||||
|  |         return FileType::XCI; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return FileType::Error; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ||||||
|  |     if (is_loaded) { | ||||||
|  |         return ResultStatus::ErrorAlreadyLoaded; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) { | ||||||
|  |         if (!Core::Crypto::KeyManager::KeyFileExists(false)) | ||||||
|  |             return ResultStatus::ErrorMissingKeys; | ||||||
|  |         return ResultStatus::ErrorDecrypting; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto result = nca_loader->Load(process); | ||||||
|  |     if (result != ResultStatus::Success) | ||||||
|  |         return result; | ||||||
|  | 
 | ||||||
|  |     is_loaded = true; | ||||||
|  | 
 | ||||||
|  |     return ResultStatus::Success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) { | ||||||
|  |     return nca_loader->ReadRomFS(dir); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) { | ||||||
|  |     return nca_loader->ReadProgramId(out_program_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Loader
 | ||||||
							
								
								
									
										44
									
								
								src/core/loader/xci.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/core/loader/xci.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | // Copyright 2018 yuzu emulator team
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "core/file_sys/card_image.h" | ||||||
|  | #include "core/loader/loader.h" | ||||||
|  | #include "core/loader/nca.h" | ||||||
|  | 
 | ||||||
|  | namespace Loader { | ||||||
|  | 
 | ||||||
|  | /// Loads an XCI file
 | ||||||
|  | class AppLoader_XCI final : public AppLoader { | ||||||
|  | public: | ||||||
|  |     explicit AppLoader_XCI(FileSys::VirtualFile file); | ||||||
|  |     ~AppLoader_XCI(); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Returns the type of the file | ||||||
|  |      * @param file std::shared_ptr<VfsFile> open file | ||||||
|  |      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||||
|  |      */ | ||||||
|  |     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||||
|  | 
 | ||||||
|  |     FileType GetFileType() override { | ||||||
|  |         return IdentifyType(file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||||
|  | 
 | ||||||
|  |     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||||||
|  |     ResultStatus ReadProgramId(u64& out_program_id) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     FileSys::ProgramMetadata metadata; | ||||||
|  | 
 | ||||||
|  |     std::unique_ptr<FileSys::XCI> xci; | ||||||
|  |     std::unique_ptr<AppLoader_NCA> nca_loader; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Loader
 | ||||||
| @ -139,6 +139,8 @@ struct Values { | |||||||
| 
 | 
 | ||||||
|     std::string log_filter; |     std::string log_filter; | ||||||
| 
 | 
 | ||||||
|  |     bool use_dev_keys; | ||||||
|  | 
 | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     std::string sink_id; |     std::string sink_id; | ||||||
|     std::string audio_device_id; |     std::string audio_device_id; | ||||||
|  | |||||||
| @ -111,6 +111,7 @@ void Config::ReadValues() { | |||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Miscellaneous"); |     qt_config->beginGroup("Miscellaneous"); | ||||||
|     Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); |     Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString(); | ||||||
|  |     Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool(); | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Debugging"); |     qt_config->beginGroup("Debugging"); | ||||||
| @ -222,6 +223,7 @@ void Config::SaveValues() { | |||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Miscellaneous"); |     qt_config->beginGroup("Miscellaneous"); | ||||||
|     qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); |     qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter)); | ||||||
|  |     qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys); | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Debugging"); |     qt_config->beginGroup("Debugging"); | ||||||
|  | |||||||
| @ -365,7 +365,7 @@ void GameList::LoadInterfaceLayout() { | |||||||
|     item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); |     item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca"}; | const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; | ||||||
| 
 | 
 | ||||||
| static bool HasSupportedFileExtension(const std::string& file_name) { | static bool HasSupportedFileExtension(const std::string& file_name) { | ||||||
|     QFileInfo file = QFileInfo(file_name.c_str()); |     QFileInfo file = QFileInfo(file_name.c_str()); | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ | |||||||
| #include "common/scope_exit.h" | #include "common/scope_exit.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| @ -424,18 +425,49 @@ bool GMainWindow::LoadROM(const QString& filename) { | |||||||
|                                   tr("Could not determine the system mode.")); |                                   tr("Could not determine the system mode.")); | ||||||
|             break; |             break; | ||||||
| 
 | 
 | ||||||
|         case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: { |         case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: { | ||||||
|  |             const auto reg_found = Core::Crypto::KeyManager::KeyFileExists(false); | ||||||
|  |             const auto title_found = Core::Crypto::KeyManager::KeyFileExists(true); | ||||||
|  | 
 | ||||||
|  |             std::string file_text; | ||||||
|  | 
 | ||||||
|  |             if (!reg_found && !title_found) { | ||||||
|  |                 file_text = "A proper key file (prod.keys, dev.keys, or title.keys) could not be " | ||||||
|  |                             "found. You will need to dump your keys from your switch to continue."; | ||||||
|  |             } else if (reg_found && title_found) { | ||||||
|  |                 file_text = | ||||||
|  |                     "Both key files were found in your config directory, but the correct key could" | ||||||
|  |                     "not be found. You may be missing a titlekey or general key, depending on " | ||||||
|  |                     "the game."; | ||||||
|  |             } else if (reg_found) { | ||||||
|  |                 file_text = | ||||||
|  |                     "The regular keys file (prod.keys/dev.keys) was found in your config, but the " | ||||||
|  |                     "titlekeys file (title.keys) was not. You are either missing the correct " | ||||||
|  |                     "titlekey or missing a general key required to decrypt the game."; | ||||||
|  |             } else { | ||||||
|  |                 file_text = "The title keys file (title.keys) was found in your config, but " | ||||||
|  |                             "the regular keys file (prod.keys/dev.keys) was not. Unfortunately, " | ||||||
|  |                             "having the titlekey is not enough, you need additional general keys " | ||||||
|  |                             "to properly decrypt the game. You should double-check to make sure " | ||||||
|  |                             "your keys are correct."; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             QMessageBox::critical( |             QMessageBox::critical( | ||||||
|                 this, tr("Error while loading ROM!"), |                 this, tr("Error while loading ROM!"), | ||||||
|                 tr("The game that you are trying to load must be decrypted before being used with " |                 tr(("The game you are trying to load is encrypted and the required keys to load " | ||||||
|                    "yuzu. A real Switch is required.<br/><br/>" |                     "the game could not be found in your configuration. " + | ||||||
|                    "For more information on dumping and decrypting games, please see the following " |                     file_text + " Please refer to the yuzu wiki for help.") | ||||||
|                    "wiki pages: <ul>" |                        .c_str())); | ||||||
|                    "<li><a href='https://yuzu-emu.org/wiki/dumping-game-cartridges/'>Dumping Game " |             break; | ||||||
|                    "Cartridges</a></li>" |         } | ||||||
|                    "<li><a href='https://yuzu-emu.org/wiki/dumping-installed-titles/'>Dumping " |         case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: { | ||||||
|                    "Installed Titles</a></li>" |             QMessageBox::critical( | ||||||
|                    "</ul>")); |                 this, tr("Error while loading ROM!"), | ||||||
|  |                 tr("There was a general error while decrypting the game. This means that the keys " | ||||||
|  |                    "necessary were found, but were either incorrect, the game itself was not a " | ||||||
|  |                    "valid game or the game uses an unhandled cryptographic scheme. Please double " | ||||||
|  |                    "check that you have the correct " | ||||||
|  |                    "keys.")); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: |         case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: | ||||||
|  | |||||||
| @ -119,6 +119,7 @@ void Config::ReadValues() { | |||||||
| 
 | 
 | ||||||
|     // Miscellaneous
 |     // Miscellaneous
 | ||||||
|     Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); |     Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); | ||||||
|  |     Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); | ||||||
| 
 | 
 | ||||||
|     // Debugging
 |     // Debugging
 | ||||||
|     Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); |     Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); | ||||||
|  | |||||||
| @ -23,6 +23,7 @@ | |||||||
| #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | #include "yuzu_cmd/emu_window/emu_window_sdl2.h" | ||||||
| 
 | 
 | ||||||
| #include <getopt.h> | #include <getopt.h> | ||||||
|  | #include "core/crypto/key_manager.h" | ||||||
| #ifndef _MSC_VER | #ifndef _MSC_VER | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #endif | #endif | ||||||
| @ -71,6 +72,7 @@ static void InitializeLogging() { | |||||||
| /// Application entry point
 | /// Application entry point
 | ||||||
| int main(int argc, char** argv) { | int main(int argc, char** argv) { | ||||||
|     Config config; |     Config config; | ||||||
|  | 
 | ||||||
|     int option_index = 0; |     int option_index = 0; | ||||||
|     bool use_gdbstub = Settings::values.use_gdbstub; |     bool use_gdbstub = Settings::values.use_gdbstub; | ||||||
|     u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); |     u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); | ||||||
| @ -171,11 +173,15 @@ int main(int argc, char** argv) { | |||||||
|     case Core::System::ResultStatus::ErrorLoader: |     case Core::System::ResultStatus::ErrorLoader: | ||||||
|         LOG_CRITICAL(Frontend, "Failed to load ROM!"); |         LOG_CRITICAL(Frontend, "Failed to load ROM!"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: |     case Core::System::ResultStatus::ErrorLoader_ErrorMissingKeys: | ||||||
|         LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " |         LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and the keys required " | ||||||
|                                "being used with yuzu. \n\n For more information on dumping and " |                                "could not be found. Please refer to the yuzu wiki for help"); | ||||||
|                                "decrypting games, please refer to: " |         return -1; | ||||||
|                                "https://yuzu-emu.org/wiki/dumping-game-cartridges/"); |     case Core::System::ResultStatus::ErrorLoader_ErrorDecrypting: | ||||||
|  |         LOG_CRITICAL(Frontend, "The game you are trying to load is encrypted and there was a " | ||||||
|  |                                "general error while decrypting. This could mean that the keys are " | ||||||
|  |                                "incorrect, game is invalid or game uses an unsupported method of " | ||||||
|  |                                "crypto. Please double-check your keys"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: |     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: | ||||||
|         LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); |         LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei