mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 22:00:05 +00:00 
			
		
		
		
	ncch_container: support encrypted games
This commit is contained in:
		
							parent
							
								
									b92660435c
								
							
						
					
					
						commit
						d4a808c885
					
				
					 17 changed files with 319 additions and 103 deletions
				
			
		|  | @ -74,14 +74,11 @@ ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path, | |||
|     // NCCH RomFS
 | ||||
|     NCCHFilePathType filepath_type = static_cast<NCCHFilePathType>(openfile_path.filepath_type); | ||||
|     if (filepath_type == NCCHFilePathType::RomFS) { | ||||
|         std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|         u64 romfs_offset = 0; | ||||
|         u64 romfs_size = 0; | ||||
|         std::shared_ptr<RomFSReader> romfs_file; | ||||
| 
 | ||||
|         result = ncch_container.ReadRomFS(romfs_file, romfs_offset, romfs_size); | ||||
|         result = ncch_container.ReadRomFS(romfs_file); | ||||
|         std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<RomFSDelayGenerator>(); | ||||
|         file = std::make_unique<IVFCFile>(std::move(romfs_file), romfs_offset, romfs_size, | ||||
|                                           std::move(delay_generator)); | ||||
|         file = std::make_unique<IVFCFile>(std::move(romfs_file), std::move(delay_generator)); | ||||
|     } else if (filepath_type == NCCHFilePathType::Code || | ||||
|                filepath_type == NCCHFilePathType::ExeFS) { | ||||
|         std::vector<u8> buffer; | ||||
|  |  | |||
|  | @ -174,8 +174,7 @@ private: | |||
|             std::unique_ptr<DelayGenerator> delay_generator = | ||||
|                 std::make_unique<RomFSDelayGenerator>(); | ||||
|             return MakeResult<std::unique_ptr<FileBackend>>( | ||||
|                 std::make_unique<IVFCFile>(ncch_data.romfs_file, ncch_data.romfs_offset, | ||||
|                                            ncch_data.romfs_size, std::move(delay_generator))); | ||||
|                 std::make_unique<IVFCFile>(ncch_data.romfs_file, std::move(delay_generator))); | ||||
|         } else { | ||||
|             LOG_INFO(Service_FS, "Unable to read RomFS"); | ||||
|             return ERROR_ROMFS_NOT_FOUND; | ||||
|  | @ -187,8 +186,7 @@ private: | |||
|             std::unique_ptr<DelayGenerator> delay_generator = | ||||
|                 std::make_unique<RomFSDelayGenerator>(); | ||||
|             return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( | ||||
|                 ncch_data.update_romfs_file, ncch_data.update_romfs_offset, | ||||
|                 ncch_data.update_romfs_size, std::move(delay_generator))); | ||||
|                 ncch_data.update_romfs_file, std::move(delay_generator))); | ||||
|         } else { | ||||
|             LOG_INFO(Service_FS, "Unable to read update RomFS"); | ||||
|             return ERROR_ROMFS_NOT_FOUND; | ||||
|  | @ -252,17 +250,14 @@ void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) { | |||
| 
 | ||||
|     NCCHData& data = ncch_data[program_id]; | ||||
| 
 | ||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file_; | ||||
|     if (Loader::ResultStatus::Success == | ||||
|         app_loader.ReadRomFS(romfs_file_, data.romfs_offset, data.romfs_size)) { | ||||
|     std::shared_ptr<RomFSReader> romfs_file_; | ||||
|     if (Loader::ResultStatus::Success == app_loader.ReadRomFS(romfs_file_)) { | ||||
| 
 | ||||
|         data.romfs_file = std::move(romfs_file_); | ||||
|     } | ||||
| 
 | ||||
|     std::shared_ptr<FileUtil::IOFile> update_romfs_file; | ||||
|     if (Loader::ResultStatus::Success == app_loader.ReadUpdateRomFS(update_romfs_file, | ||||
|                                                                     data.update_romfs_offset, | ||||
|                                                                     data.update_romfs_size)) { | ||||
|     std::shared_ptr<RomFSReader> update_romfs_file; | ||||
|     if (Loader::ResultStatus::Success == app_loader.ReadUpdateRomFS(update_romfs_file)) { | ||||
| 
 | ||||
|         data.update_romfs_file = std::move(update_romfs_file); | ||||
|     } | ||||
|  |  | |||
|  | @ -22,13 +22,8 @@ struct NCCHData { | |||
|     std::shared_ptr<std::vector<u8>> icon; | ||||
|     std::shared_ptr<std::vector<u8>> logo; | ||||
|     std::shared_ptr<std::vector<u8>> banner; | ||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|     u64 romfs_offset = 0; | ||||
|     u64 romfs_size = 0; | ||||
| 
 | ||||
|     std::shared_ptr<FileUtil::IOFile> update_romfs_file; | ||||
|     u64 update_romfs_offset = 0; | ||||
|     u64 update_romfs_size = 0; | ||||
|     std::shared_ptr<RomFSReader> romfs_file; | ||||
|     std::shared_ptr<RomFSReader> update_romfs_file; | ||||
| }; | ||||
| 
 | ||||
| /// File system interface to the SelfNCCH archive
 | ||||
|  |  | |||
|  | @ -14,8 +14,7 @@ | |||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| IVFCArchive::IVFCArchive(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size) | ||||
|     : romfs_file(std::move(file)), data_offset(offset), data_size(size) {} | ||||
| IVFCArchive::IVFCArchive(std::shared_ptr<RomFSReader> file) : romfs_file(std::move(file)) {} | ||||
| 
 | ||||
| std::string IVFCArchive::GetName() const { | ||||
|     return "IVFC"; | ||||
|  | @ -25,7 +24,7 @@ ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, | |||
|                                                               const Mode& mode) const { | ||||
|     std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<IVFCDelayGenerator>(); | ||||
|     return MakeResult<std::unique_ptr<FileBackend>>( | ||||
|         std::make_unique<IVFCFile>(romfs_file, data_offset, data_size, std::move(delay_generator))); | ||||
|         std::make_unique<IVFCFile>(romfs_file, std::move(delay_generator))); | ||||
| } | ||||
| 
 | ||||
| ResultCode IVFCArchive::DeleteFile(const Path& path) const { | ||||
|  | @ -85,18 +84,15 @@ u64 IVFCArchive::GetFreeBytes() const { | |||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
| IVFCFile::IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size, | ||||
| IVFCFile::IVFCFile(std::shared_ptr<RomFSReader> file, | ||||
|                    std::unique_ptr<DelayGenerator> delay_generator_) | ||||
|     : romfs_file(std::move(file)), data_offset(offset), data_size(size) { | ||||
|     : romfs_file(std::move(file)) { | ||||
|     delay_generator = std::move(delay_generator_); | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> IVFCFile::Read(const u64 offset, const size_t length, u8* buffer) const { | ||||
|     LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); | ||||
|     romfs_file->Seek(data_offset + offset, SEEK_SET); | ||||
|     size_t read_length = (size_t)std::min((u64)length, data_size - offset); | ||||
| 
 | ||||
|     return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length)); | ||||
|     return MakeResult<size_t>(romfs_file->ReadFile(offset, length, buffer)); | ||||
| } | ||||
| 
 | ||||
| ResultVal<size_t> IVFCFile::Write(const u64 offset, const size_t length, const bool flush, | ||||
|  | @ -107,7 +103,7 @@ ResultVal<size_t> IVFCFile::Write(const u64 offset, const size_t length, const b | |||
| } | ||||
| 
 | ||||
| u64 IVFCFile::GetSize() const { | ||||
|     return data_size; | ||||
|     return romfs_file->GetSize(); | ||||
| } | ||||
| 
 | ||||
| bool IVFCFile::SetSize(const u64 size) const { | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "core/file_sys/archive_backend.h" | ||||
| #include "core/file_sys/directory_backend.h" | ||||
| #include "core/file_sys/file_backend.h" | ||||
| #include "core/file_sys/romfs_reader.h" | ||||
| #include "core/hle/result.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -67,7 +68,7 @@ public: | |||
|  */ | ||||
| class IVFCArchive : public ArchiveBackend { | ||||
| public: | ||||
|     IVFCArchive(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size); | ||||
|     IVFCArchive(std::shared_ptr<RomFSReader> file); | ||||
| 
 | ||||
|     std::string GetName() const override; | ||||
| 
 | ||||
|  | @ -84,15 +85,12 @@ public: | |||
|     u64 GetFreeBytes() const override; | ||||
| 
 | ||||
| protected: | ||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|     u64 data_offset; | ||||
|     u64 data_size; | ||||
|     std::shared_ptr<RomFSReader> romfs_file; | ||||
| }; | ||||
| 
 | ||||
| class IVFCFile : public FileBackend { | ||||
| public: | ||||
|     IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size, | ||||
|              std::unique_ptr<DelayGenerator> delay_generator_); | ||||
|     IVFCFile(std::shared_ptr<RomFSReader> file, std::unique_ptr<DelayGenerator> delay_generator_); | ||||
| 
 | ||||
|     ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override; | ||||
|     ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) override; | ||||
|  | @ -104,9 +102,7 @@ public: | |||
|     void Flush() const override {} | ||||
| 
 | ||||
| private: | ||||
|     std::shared_ptr<FileUtil::IOFile> romfs_file; | ||||
|     u64 data_offset; | ||||
|     u64 data_size; | ||||
|     std::shared_ptr<RomFSReader> romfs_file; | ||||
| }; | ||||
| 
 | ||||
| class IVFCDirectory : public DirectoryBackend { | ||||
|  |  | |||
|  | @ -5,10 +5,13 @@ | |||
| #include <cinttypes> | ||||
| #include <cstring> | ||||
| #include <memory> | ||||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include "common/common_types.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/ncch_container.h" | ||||
| #include "core/hw/aes/key.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -142,6 +145,116 @@ Loader::ResultStatus NCCHContainer::Load() { | |||
|             return Loader::ResultStatus::ErrorInvalidFormat; | ||||
| 
 | ||||
|         has_header = true; | ||||
|         bool failed_to_decrypt = false; | ||||
|         if (!ncch_header.no_crypto) { | ||||
|             is_encrypted = true; | ||||
| 
 | ||||
|             // Find primary and secondary keys
 | ||||
|             if (ncch_header.fixed_key) { | ||||
|                 LOG_DEBUG(Service_FS, "Fixed-key crypto"); | ||||
|                 primary_key.fill(0); | ||||
|                 secondary_key.fill(0); | ||||
|             } else { | ||||
|                 using namespace HW::AES; | ||||
|                 InitKeys(); | ||||
|                 std::array<u8, 16> key_y_primary, key_y_secondary; | ||||
| 
 | ||||
|                 std::copy(ncch_header.signature, ncch_header.signature + key_y_primary.size(), | ||||
|                           key_y_primary.begin()); | ||||
| 
 | ||||
|                 if (!ncch_header.seed_crypto) { | ||||
|                     key_y_secondary = key_y_primary; | ||||
|                 } else { | ||||
|                     // Seed crypto is unimplemented.
 | ||||
|                     LOG_ERROR(Service_FS, "Unsupported seed crypto"); | ||||
|                     failed_to_decrypt = true; | ||||
|                 } | ||||
| 
 | ||||
|                 SetKeyY(KeySlotID::NCCHSecure1, key_y_primary); | ||||
|                 if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure1)) { | ||||
|                     LOG_ERROR(Service_FS, "Secure1 KeyX missing"); | ||||
|                     failed_to_decrypt = true; | ||||
|                 } | ||||
|                 primary_key = GetNormalKey(KeySlotID::NCCHSecure1); | ||||
| 
 | ||||
|                 switch (ncch_header.secondary_key_slot) { | ||||
|                 case 0: | ||||
|                     LOG_DEBUG(Service_FS, "Secure1 crypto"); | ||||
|                     secondary_key = primary_key; | ||||
|                     break; | ||||
|                 case 1: | ||||
|                     LOG_DEBUG(Service_FS, "Secure2 crypto"); | ||||
|                     SetKeyY(KeySlotID::NCCHSecure2, key_y_secondary); | ||||
|                     if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure2)) { | ||||
|                         LOG_ERROR(Service_FS, "Secure2 KeyX missing"); | ||||
|                         failed_to_decrypt = true; | ||||
|                     } | ||||
|                     secondary_key = GetNormalKey(KeySlotID::NCCHSecure2); | ||||
|                     break; | ||||
|                 case 10: | ||||
|                     LOG_DEBUG(Service_FS, "Secure3 crypto"); | ||||
|                     SetKeyY(KeySlotID::NCCHSecure3, key_y_secondary); | ||||
|                     if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure3)) { | ||||
|                         LOG_ERROR(Service_FS, "Secure3 KeyX missing"); | ||||
|                         failed_to_decrypt = true; | ||||
|                     } | ||||
|                     secondary_key = GetNormalKey(KeySlotID::NCCHSecure3); | ||||
|                     break; | ||||
|                 case 11: | ||||
|                     LOG_DEBUG(Service_FS, "Secure4 crypto"); | ||||
|                     SetKeyY(KeySlotID::NCCHSecure4, key_y_secondary); | ||||
|                     if (!IsNormalKeyAvailable(KeySlotID::NCCHSecure4)) { | ||||
|                         LOG_ERROR(Service_FS, "Secure4 KeyX missing"); | ||||
|                         failed_to_decrypt = true; | ||||
|                     } | ||||
|                     secondary_key = GetNormalKey(KeySlotID::NCCHSecure4); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Find CTR for each section
 | ||||
|             // Written with reference to
 | ||||
|             // https://github.com/d0k3/GodMode9/blob/99af6a73be48fa7872649aaa7456136da0df7938/arm9/source/game/ncch.c#L34-L52
 | ||||
|             if (ncch_header.version == 0 || ncch_header.version == 2) { | ||||
|                 LOG_DEBUG(Loader, "NCCH version 0/2"); | ||||
|                 // In this version, CTR for each section is a magic number prefixed by partition ID
 | ||||
|                 // (reverse order)
 | ||||
|                 std::reverse_copy(ncch_header.partition_id, ncch_header.partition_id + 8, | ||||
|                                   exheader_ctr.begin()); | ||||
|                 exefs_ctr = romfs_ctr = exheader_ctr; | ||||
|                 exheader_ctr[8] = 1; | ||||
|                 exefs_ctr[8] = 2; | ||||
|                 romfs_ctr[8] = 3; | ||||
|             } else if (ncch_header.version == 1) { | ||||
|                 LOG_DEBUG(Loader, "NCCH version 1"); | ||||
|                 // In this version, CTR for each section is the section offset prefixed by partition
 | ||||
|                 // ID, as if the entire NCCH image is encrypted using a single CTR stream.
 | ||||
|                 std::copy(ncch_header.partition_id, ncch_header.partition_id + 8, | ||||
|                           exheader_ctr.begin()); | ||||
|                 exefs_ctr = romfs_ctr = exheader_ctr; | ||||
|                 auto u32ToBEArray = [](u32 value) -> std::array<u8, 4> { | ||||
|                     return std::array<u8, 4>{ | ||||
|                         static_cast<u8>(value >> 24), | ||||
|                         static_cast<u8>((value >> 16) & 0xFF), | ||||
|                         static_cast<u8>((value >> 8) & 0xFF), | ||||
|                         static_cast<u8>(value & 0xFF), | ||||
|                     }; | ||||
|                 }; | ||||
|                 auto offset_exheader = u32ToBEArray(0x200); // exheader offset
 | ||||
|                 auto offset_exefs = u32ToBEArray(ncch_header.exefs_offset * kBlockSize); | ||||
|                 auto offset_romfs = u32ToBEArray(ncch_header.romfs_offset * kBlockSize); | ||||
|                 std::copy(offset_exheader.begin(), offset_exheader.end(), | ||||
|                           exheader_ctr.begin() + 12); | ||||
|                 std::copy(offset_exefs.begin(), offset_exefs.end(), exefs_ctr.begin() + 12); | ||||
|                 std::copy(offset_romfs.begin(), offset_romfs.end(), romfs_ctr.begin() + 12); | ||||
|             } else { | ||||
|                 LOG_ERROR(Service_FS, "Unknown NCCH version {}", ncch_header.version); | ||||
|                 failed_to_decrypt = true; | ||||
|             } | ||||
|         } else { | ||||
|             LOG_DEBUG(Service_FS, "No crypto"); | ||||
|             is_encrypted = false; | ||||
|         } | ||||
| 
 | ||||
|         // System archives and DLC don't have an extended header but have RomFS
 | ||||
|         if (ncch_header.extended_header_size) { | ||||
|  | @ -149,6 +262,26 @@ Loader::ResultStatus NCCHContainer::Load() { | |||
|                 sizeof(ExHeader_Header)) | ||||
|                 return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|             if (is_encrypted) { | ||||
|                 // This ID check is masked to low 32-bit as a toleration to ill-formed ROM created
 | ||||
|                 // by merging games and its updates.
 | ||||
|                 if ((exheader_header.system_info.jump_id & 0xFFFFFFFF) == | ||||
|                     (ncch_header.program_id & 0xFFFFFFFF)) { | ||||
|                     LOG_WARNING(Service_FS, "NCCH is marked as encrypted but with decrypted " | ||||
|                                             "exheader. Force no crypto scheme."); | ||||
|                     is_encrypted = false; | ||||
|                 } else { | ||||
|                     if (failed_to_decrypt) { | ||||
|                         LOG_ERROR(Service_FS, "Failed to decrypt"); | ||||
|                         return Loader::ResultStatus::ErrorEncrypted; | ||||
|                     } | ||||
|                     CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exheader_header); | ||||
|                     CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption( | ||||
|                         primary_key.data(), primary_key.size(), exheader_ctr.data()) | ||||
|                         .ProcessData(data, data, sizeof(exheader_header)); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; | ||||
|             u32 entry_point = exheader_header.codeset_info.text.address; | ||||
|             u32 code_size = exheader_header.codeset_info.text.code_size; | ||||
|  | @ -173,12 +306,6 @@ Loader::ResultStatus NCCHContainer::Load() { | |||
|             LOG_DEBUG(Service_FS, "System Mode:                 {}", | ||||
|                       static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); | ||||
| 
 | ||||
|             if (exheader_header.system_info.jump_id != ncch_header.program_id) { | ||||
|                 LOG_ERROR(Service_FS, | ||||
|                           "ExHeader Program ID mismatch: the ROM is probably encrypted."); | ||||
|                 return Loader::ResultStatus::ErrorEncrypted; | ||||
|             } | ||||
| 
 | ||||
|             has_exheader = true; | ||||
|         } | ||||
| 
 | ||||
|  | @ -194,6 +321,13 @@ Loader::ResultStatus NCCHContainer::Load() { | |||
|             if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) | ||||
|                 return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|             if (is_encrypted) { | ||||
|                 CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(&exefs_header); | ||||
|                 CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption(primary_key.data(), | ||||
|                                                               primary_key.size(), exefs_ctr.data()) | ||||
|                     .ProcessData(data, data, sizeof(exefs_header)); | ||||
|             } | ||||
| 
 | ||||
|             exefs_file = FileUtil::IOFile(filepath, "rb"); | ||||
|             has_exefs = true; | ||||
|         } | ||||
|  | @ -293,6 +427,17 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect | |||
|                 (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); | ||||
|             exefs_file.Seek(section_offset, SEEK_SET); | ||||
| 
 | ||||
|             std::array<u8, 16> key; | ||||
|             if (strcmp(section.name, "icon") == 0 || strcmp(section.name, "banner") == 0) { | ||||
|                 key = primary_key; | ||||
|             } else { | ||||
|                 key = secondary_key; | ||||
|             } | ||||
| 
 | ||||
|             CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption dec(key.data(), key.size(), | ||||
|                                                               exefs_ctr.data()); | ||||
|             dec.Seek(section.offset + sizeof(ExeFs_Header)); | ||||
| 
 | ||||
|             if (strcmp(section.name, ".code") == 0 && is_compressed) { | ||||
|                 // Section is compressed, read compressed .code section...
 | ||||
|                 std::unique_ptr<u8[]> temp_buffer; | ||||
|  | @ -305,6 +450,10 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect | |||
|                 if (exefs_file.ReadBytes(&temp_buffer[0], section.size) != section.size) | ||||
|                     return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|                 if (is_encrypted) { | ||||
|                     dec.ProcessData(&temp_buffer[0], &temp_buffer[0], section.size); | ||||
|                 } | ||||
| 
 | ||||
|                 // Decompress .code section...
 | ||||
|                 u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); | ||||
|                 buffer.resize(decompressed_size); | ||||
|  | @ -315,6 +464,9 @@ Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vect | |||
|                 buffer.resize(section.size); | ||||
|                 if (exefs_file.ReadBytes(&buffer[0], section.size) != section.size) | ||||
|                     return Loader::ResultStatus::Error; | ||||
|                 if (is_encrypted) { | ||||
|                     dec.ProcessData(&buffer[0], &buffer[0], section.size); | ||||
|                 } | ||||
|             } | ||||
|             return Loader::ResultStatus::Success; | ||||
|         } | ||||
|  | @ -354,13 +506,12 @@ Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name, | |||
|     return Loader::ResultStatus::ErrorNotUsed; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||||
|                                               u64& offset, u64& size) { | ||||
| Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file) { | ||||
|     Loader::ResultStatus result = Load(); | ||||
|     if (result != Loader::ResultStatus::Success) | ||||
|         return result; | ||||
| 
 | ||||
|     if (ReadOverrideRomFS(romfs_file, offset, size) == Loader::ResultStatus::Success) | ||||
|     if (ReadOverrideRomFS(romfs_file) == Loader::ResultStatus::Success) | ||||
|         return Loader::ResultStatus::Success; | ||||
| 
 | ||||
|     if (!has_romfs) { | ||||
|  | @ -381,26 +532,30 @@ Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& | |||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     // We reopen the file, to allow its position to be independent from file's
 | ||||
|     romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); | ||||
|     if (!romfs_file->IsOpen()) | ||||
|     FileUtil::IOFile romfs_file_inner(filepath, "rb"); | ||||
|     if (!romfs_file_inner.IsOpen()) | ||||
|         return Loader::ResultStatus::Error; | ||||
| 
 | ||||
|     offset = romfs_offset; | ||||
|     size = romfs_size; | ||||
|     if (is_encrypted) { | ||||
|         romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, | ||||
|                                                    romfs_size, secondary_key, romfs_ctr, 0x1000); | ||||
|     } else { | ||||
|         romfs_file = | ||||
|             std::make_shared<RomFSReader>(std::move(romfs_file_inner), romfs_offset, romfs_size); | ||||
|     } | ||||
| 
 | ||||
|     return Loader::ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||||
|                                                       u64& offset, u64& size) { | ||||
| Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReader>& romfs_file) { | ||||
|     // Check for RomFS overrides
 | ||||
|     std::string split_filepath = filepath + ".romfs"; | ||||
|     if (FileUtil::Exists(split_filepath)) { | ||||
|         romfs_file = std::make_shared<FileUtil::IOFile>(split_filepath, "rb"); | ||||
|         if (romfs_file->IsOpen()) { | ||||
|         FileUtil::IOFile romfs_file_inner(split_filepath, "rb"); | ||||
|         if (romfs_file_inner.IsOpen()) { | ||||
|             LOG_WARNING(Service_FS, "File {} overriding built-in RomFS", split_filepath); | ||||
|             offset = 0; | ||||
|             size = romfs_file->GetSize(); | ||||
|             romfs_file = std::make_shared<RomFSReader>(std::move(romfs_file_inner), 0, | ||||
|                                                        romfs_file_inner.GetSize()); | ||||
|             return Loader::ResultStatus::Success; | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ | |||
| #include "common/file_util.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/romfs_reader.h" | ||||
| 
 | ||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||
| /// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym)
 | ||||
|  | @ -32,7 +33,28 @@ struct NCCH_Header { | |||
|     u8 extended_header_hash[0x20]; | ||||
|     u32_le extended_header_size; | ||||
|     u8 reserved_2[4]; | ||||
|     u8 flags[8]; | ||||
|     u8 reserved_flag[3]; | ||||
|     u8 secondary_key_slot; | ||||
|     u8 platform; | ||||
|     enum class ContentType : u8 { | ||||
|         Application = 0, | ||||
|         SystemUpdate = 1, | ||||
|         Manual = 2, | ||||
|         Child = 3, | ||||
|         Trial = 4, | ||||
|     }; | ||||
|     union { | ||||
|         BitField<0, 1, u8> is_data; | ||||
|         BitField<1, 1, u8> is_executable; | ||||
|         BitField<2, 3, ContentType> content_type; | ||||
|     }; | ||||
|     u8 content_unit_size; | ||||
|     union { | ||||
|         BitField<0, 1, u8> fixed_key; | ||||
|         BitField<1, 1, u8> no_romfs; | ||||
|         BitField<2, 1, u8> no_crypto; | ||||
|         BitField<5, 1, u8> seed_crypto; | ||||
|     }; | ||||
|     u32_le plain_region_offset; | ||||
|     u32_le plain_region_size; | ||||
|     u32_le logo_region_offset; | ||||
|  | @ -211,8 +233,7 @@ public: | |||
|      * @param size The size of the romfs | ||||
|      * @return ResultStatus result of function | ||||
|      */ | ||||
|     Loader::ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                                    u64& size); | ||||
|     Loader::ResultStatus ReadRomFS(std::shared_ptr<RomFSReader>& romfs_file); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the override RomFS of the NCCH container | ||||
|  | @ -222,8 +243,7 @@ public: | |||
|      * @param size The size of the romfs | ||||
|      * @return ResultStatus result of function | ||||
|      */ | ||||
|     Loader::ResultStatus ReadOverrideRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||||
|                                            u64& offset, u64& size); | ||||
|     Loader::ResultStatus ReadOverrideRomFS(std::shared_ptr<RomFSReader>& romfs_file); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Get the Program ID of the NCCH container | ||||
|  | @ -263,6 +283,14 @@ private: | |||
|     bool is_loaded = false; | ||||
|     bool is_compressed = false; | ||||
| 
 | ||||
|     bool is_encrypted = false; | ||||
|     // for decrypting exheader, exefs header and icon/banner section
 | ||||
|     std::array<u8, 16> primary_key{}; | ||||
|     std::array<u8, 16> secondary_key{}; // for decrypting romfs and .code section
 | ||||
|     std::array<u8, 16> exheader_ctr{}; | ||||
|     std::array<u8, 16> exefs_ctr{}; | ||||
|     std::array<u8, 16> romfs_ctr{}; | ||||
| 
 | ||||
|     u32 ncch_offset = 0; // Offset to NCCH header, can be 0 for NCCHs or non-zero for CIAs/NCSDs
 | ||||
|     u32 exefs_offset = 0; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										22
									
								
								src/core/file_sys/romfs_reader.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/core/file_sys/romfs_reader.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| #include <algorithm> | ||||
| #include <cryptopp/aes.h> | ||||
| #include <cryptopp/modes.h> | ||||
| #include "core/file_sys/romfs_reader.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| std::size_t RomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) { | ||||
|     if (length == 0) | ||||
|         return 0; // Crypto++ does not like zero size buffer
 | ||||
|     file.Seek(file_offset + offset, SEEK_SET); | ||||
|     std::size_t read_length = std::min(length, data_size - offset); | ||||
|     read_length = file.ReadBytes(buffer, read_length); | ||||
|     if (is_encrypted) { | ||||
|         CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data()); | ||||
|         d.Seek(crypto_offset + offset); | ||||
|         d.ProcessData(buffer, buffer, read_length); | ||||
|     } | ||||
|     return read_length; | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										37
									
								
								src/core/file_sys/romfs_reader.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/core/file_sys/romfs_reader.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include "common/common_types.h" | ||||
| #include "common/file_util.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| class RomFSReader { | ||||
| public: | ||||
|     RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size) | ||||
|         : is_encrypted(false), file(std::move(file)), file_offset(file_offset), | ||||
|           data_size(data_size) {} | ||||
| 
 | ||||
|     RomFSReader(FileUtil::IOFile&& file, std::size_t file_offset, std::size_t data_size, | ||||
|                 const std::array<u8, 16>& key, const std::array<u8, 16>& ctr, | ||||
|                 std::size_t crypto_offset) | ||||
|         : is_encrypted(true), file(std::move(file)), key(key), ctr(ctr), file_offset(file_offset), | ||||
|           crypto_offset(crypto_offset), data_size(data_size) {} | ||||
| 
 | ||||
|     std::size_t GetSize() const { | ||||
|         return data_size; | ||||
|     } | ||||
| 
 | ||||
|     std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer); | ||||
| 
 | ||||
| private: | ||||
|     bool is_encrypted; | ||||
|     FileUtil::IOFile file; | ||||
|     std::array<u8, 16> key; | ||||
|     std::array<u8, 16> ctr; | ||||
|     std::size_t file_offset; | ||||
|     std::size_t crypto_offset; | ||||
|     std::size_t data_size; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue