mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Add option to configure to download system files from Nintendo Update Service (#6269)
Co-authored-by: B3n30 <benediktthomas@gmail.com>
This commit is contained in:
		
							parent
							
								
									691cb43871
								
							
						
					
					
						commit
						6bef34852c
					
				
					 16 changed files with 1076 additions and 10 deletions
				
			
		|  | @ -16,8 +16,6 @@ | |||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; | ||||
| 
 | ||||
| Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) { | ||||
|     std::vector<u8> header_data(sizeof(Header)); | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8); | |||
| constexpr std::size_t CIA_HEADER_SIZE = 0x2020; | ||||
| constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300; | ||||
| constexpr std::size_t CIA_METADATA_SIZE = 0x400; | ||||
| constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; | ||||
| 
 | ||||
| /**
 | ||||
|  * Helper which implements an interface to read and write CTR Installable Archive (CIA) files. | ||||
|  | @ -69,7 +70,6 @@ public: | |||
| 
 | ||||
|     void Print() const; | ||||
| 
 | ||||
| private: | ||||
|     struct Header { | ||||
|         u32_le header_size; | ||||
|         u16_le type; | ||||
|  | @ -87,10 +87,14 @@ private: | |||
|             // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc.
 | ||||
|             return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0; | ||||
|         } | ||||
|         void SetContentPresent(u16 index) { | ||||
|             content_present[index >> 3] |= (0x80 >> (index & 7)); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); | ||||
| 
 | ||||
| private: | ||||
|     struct Metadata { | ||||
|         std::array<u64_le, 0x30> dependencies; | ||||
|         std::array<u8, 0x180> reserved; | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include <fmt/format.h> | ||||
| #include "common/alignment.h" | ||||
| #include "common/common_paths.h" | ||||
| #include "common/file_util.h" | ||||
| #include "common/logging/log.h" | ||||
|  | @ -31,6 +32,9 @@ | |||
| #include "core/hle/service/fs/fs_user.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/loader/smdh.h" | ||||
| #ifdef ENABLE_WEB_SERVICE | ||||
| #include "web_service/nus_download.h" | ||||
| #endif | ||||
| 
 | ||||
| namespace Service::AM { | ||||
| 
 | ||||
|  | @ -138,6 +142,8 @@ ResultCode CIAFile::WriteTitleMetadata() { | |||
|             decryption_state->content[i].SetKeyWithIV(title_key->data(), title_key->size(), | ||||
|                                                       ctr.data()); | ||||
|         } | ||||
|     } else { | ||||
|         LOG_ERROR(Service_AM, "Can't get title key from ticket"); | ||||
|     } | ||||
| 
 | ||||
|     install_state = CIAInstallState::TMDLoaded; | ||||
|  | @ -180,6 +186,11 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length, | |||
|                                  buffer + (range_min - offset) + available_to_write); | ||||
| 
 | ||||
|             if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { | ||||
|                 if (decryption_state->content.size() <= i) { | ||||
|                     // TODO: There is probably no correct error to return here. What error should be
 | ||||
|                     // returned?
 | ||||
|                     return FileSys::ERROR_INSUFFICIENT_SPACE; | ||||
|                 } | ||||
|                 decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size()); | ||||
|             } | ||||
| 
 | ||||
|  | @ -235,7 +246,7 @@ ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush | |||
|         std::size_t buf_offset = buf_loaded - offset; | ||||
|         std::size_t buf_copy_size = | ||||
|             std::min(length, static_cast<std::size_t>(container.GetContentOffset() - offset)) - | ||||
|             buf_loaded; | ||||
|             buf_offset; | ||||
|         std::size_t buf_max_size = std::min(offset + length, container.GetContentOffset()); | ||||
|         data.resize(buf_max_size); | ||||
|         memcpy(data.data() + copy_offset, buffer + buf_offset, buf_copy_size); | ||||
|  | @ -418,6 +429,99 @@ InstallStatus InstallCIA(const std::string& path, | |||
|     return InstallStatus::ErrorInvalid; | ||||
| } | ||||
| 
 | ||||
| InstallStatus InstallFromNus(u64 title_id, int version) { | ||||
| #ifdef ENABLE_WEB_SERVICE | ||||
|     LOG_DEBUG(Service_AM, "Downloading {:X}", title_id); | ||||
| 
 | ||||
|     CIAFile install_file{GetTitleMediaType(title_id)}; | ||||
| 
 | ||||
|     std::string path = fmt::format("/ccs/download/{:016X}/tmd", title_id); | ||||
|     if (version != -1) { | ||||
|         path += fmt::format(".{}", version); | ||||
|     } | ||||
|     auto tmd_response = WebService::NUS::Download(path); | ||||
|     if (!tmd_response) { | ||||
|         LOG_ERROR(Service_AM, "Failed to download tmd for {:016X}", title_id); | ||||
|         return InstallStatus::ErrorFileNotFound; | ||||
|     } | ||||
|     FileSys::TitleMetadata tmd; | ||||
|     tmd.Load(*tmd_response); | ||||
| 
 | ||||
|     path = fmt::format("/ccs/download/{:016X}/cetk", title_id); | ||||
|     auto cetk_response = WebService::NUS::Download(path); | ||||
|     if (!cetk_response) { | ||||
|         LOG_ERROR(Service_AM, "Failed to download cetk for {:016X}", title_id); | ||||
|         return InstallStatus::ErrorFileNotFound; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8> content; | ||||
|     const auto content_count = tmd.GetContentCount(); | ||||
|     for (std::size_t i = 0; i < content_count; ++i) { | ||||
|         const std::string filename = fmt::format("{:08x}", tmd.GetContentIDByIndex(i)); | ||||
|         path = fmt::format("/ccs/download/{:016X}/{}", title_id, filename); | ||||
|         const auto temp_response = WebService::NUS::Download(path); | ||||
|         if (!temp_response) { | ||||
|             LOG_ERROR(Service_AM, "Failed to download content for {:016X}", title_id); | ||||
|             return InstallStatus::ErrorFileNotFound; | ||||
|         } | ||||
|         content.insert(content.end(), temp_response->begin(), temp_response->end()); | ||||
|     } | ||||
| 
 | ||||
|     FileSys::CIAContainer::Header fake_header{ | ||||
|         .header_size = sizeof(FileSys::CIAContainer::Header), | ||||
|         .type = 0, | ||||
|         .version = 0, | ||||
|         .cert_size = 0, | ||||
|         .tik_size = static_cast<u32_le>(cetk_response->size()), | ||||
|         .tmd_size = static_cast<u32_le>(tmd_response->size()), | ||||
|         .meta_size = 0, | ||||
|     }; | ||||
|     for (u16 i = 0; i < content_count; ++i) { | ||||
|         fake_header.SetContentPresent(i); | ||||
|     } | ||||
|     std::vector<u8> header_data(sizeof(fake_header)); | ||||
|     std::memcpy(header_data.data(), &fake_header, sizeof(fake_header)); | ||||
| 
 | ||||
|     std::size_t current_offset = 0; | ||||
|     const auto write_to_cia_file_aligned = [&install_file, ¤t_offset](std::vector<u8>& data) { | ||||
|         const u64 offset = | ||||
|             Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT); | ||||
|         data.resize(offset - current_offset, 0); | ||||
|         const auto result = install_file.Write(current_offset, data.size(), true, data.data()); | ||||
|         if (result.Failed()) { | ||||
|             LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}", | ||||
|                       result.Code().raw); | ||||
|             return InstallStatus::ErrorAborted; | ||||
|         } | ||||
|         current_offset += data.size(); | ||||
|         return InstallStatus::Success; | ||||
|     }; | ||||
| 
 | ||||
|     auto result = write_to_cia_file_aligned(header_data); | ||||
|     if (result != InstallStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     result = write_to_cia_file_aligned(*cetk_response); | ||||
|     if (result != InstallStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     result = write_to_cia_file_aligned(*tmd_response); | ||||
|     if (result != InstallStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     result = write_to_cia_file_aligned(content); | ||||
|     if (result != InstallStatus::Success) { | ||||
|         return result; | ||||
|     } | ||||
|     return InstallStatus::Success; | ||||
| #else | ||||
|     return InstallStatus::ErrorFileNotFound; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| Service::FS::MediaType GetTitleMediaType(u64 titleId) { | ||||
|     u16 platform = static_cast<u16>(titleId >> 48); | ||||
|     u16 category = static_cast<u16>((titleId >> 32) & 0xFFFF); | ||||
|  |  | |||
|  | @ -110,6 +110,13 @@ private: | |||
| InstallStatus InstallCIA(const std::string& path, | ||||
|                          std::function<ProgressCallback>&& update_callback = nullptr); | ||||
| 
 | ||||
| /**
 | ||||
|  * Downloads and installs title form the Nintendo Update Service. | ||||
|  * @param title_id the title_id to download | ||||
|  * @returns  whether the install was successful or error code | ||||
|  */ | ||||
| InstallStatus InstallFromNus(u64 title_id, int version = -1); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the mediatype for an installed title | ||||
|  * @param titleId the installed title ID | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ struct KeySlot { | |||
| }; | ||||
| 
 | ||||
| std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots; | ||||
| std::array<std::optional<AESKey>, 6> common_key_y_slots; | ||||
| std::array<std::optional<AESKey>, MaxCommonKeySlot> common_key_y_slots; | ||||
| 
 | ||||
| enum class FirmwareType : u32 { | ||||
|     ARM9 = 0,  // uses NDMA
 | ||||
|  | @ -494,9 +494,9 @@ void LoadPresetKeys() { | |||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void InitKeys() { | ||||
| void InitKeys(bool force) { | ||||
|     static bool initialized = false; | ||||
|     if (initialized) | ||||
|     if (initialized && !force) | ||||
|         return; | ||||
|     initialized = true; | ||||
|     HW::RSA::InitSlots(); | ||||
|  |  | |||
|  | @ -48,11 +48,13 @@ enum KeySlotID : std::size_t { | |||
|     MaxKeySlotID = 0x40, | ||||
| }; | ||||
| 
 | ||||
| constexpr std::size_t MaxCommonKeySlot = 6; | ||||
| 
 | ||||
| constexpr std::size_t AES_BLOCK_SIZE = 16; | ||||
| 
 | ||||
| using AESKey = std::array<u8, AES_BLOCK_SIZE>; | ||||
| 
 | ||||
| void InitKeys(); | ||||
| void InitKeys(bool force = false); | ||||
| 
 | ||||
| void SetGeneratorConstant(const AESKey& key); | ||||
| void SetKeyX(std::size_t slot_id, const AESKey& key); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue