ncch_container: support encrypted games
This commit is contained in:
		| @@ -64,6 +64,8 @@ add_library(core STATIC | ||||
|     file_sys/ncch_container.h | ||||
|     file_sys/path_parser.cpp | ||||
|     file_sys/path_parser.h | ||||
|     file_sys/romfs_reader.cpp | ||||
|     file_sys/romfs_reader.h | ||||
|     file_sys/savedata_archive.cpp | ||||
|     file_sys/savedata_archive.h | ||||
|     file_sys/title_metadata.cpp | ||||
|   | ||||
| @@ -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 | ||||
| @@ -56,13 +56,6 @@ struct KeySlot { | ||||
|  | ||||
| std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots; | ||||
|  | ||||
| void ClearAllKeys() { | ||||
|     for (KeySlot& slot : key_slots) { | ||||
|         slot.Clear(); | ||||
|     } | ||||
|     generator_constant.reset(); | ||||
| } | ||||
|  | ||||
| AESKey HexToKey(const std::string& hex) { | ||||
|     if (hex.size() < 32) { | ||||
|         throw std::invalid_argument("hex string is too short"); | ||||
| @@ -141,8 +134,11 @@ void LoadPresetKeys() { | ||||
| } // namespace | ||||
|  | ||||
| void InitKeys() { | ||||
|     ClearAllKeys(); | ||||
|     static bool initialized = false; | ||||
|     if (initialized) | ||||
|         return; | ||||
|     LoadPresetKeys(); | ||||
|     initialized = true; | ||||
| } | ||||
|  | ||||
| void SetGeneratorConstant(const AESKey& key) { | ||||
|   | ||||
| @@ -12,8 +12,16 @@ namespace HW { | ||||
| namespace AES { | ||||
|  | ||||
| enum KeySlotID : size_t { | ||||
|     // AES Keyslot used to generate the UDS data frame CCMP key. | ||||
|     // AES keyslots used to decrypt NCCH | ||||
|     NCCHSecure1 = 0x2C, | ||||
|     NCCHSecure2 = 0x25, | ||||
|     NCCHSecure3 = 0x18, | ||||
|     NCCHSecure4 = 0x1B, | ||||
|  | ||||
|     // AES keyslot used to generate the UDS data frame CCMP key. | ||||
|     UDSDataKey = 0x2D, | ||||
|  | ||||
|     // AES keyslot used for APT:Wrap/Unwrap functions | ||||
|     APTWrap = 0x31, | ||||
|  | ||||
|     MaxKeySlotID = 0x40, | ||||
|   | ||||
| @@ -283,8 +283,7 @@ ResultStatus AppLoader_THREEDSX::Load(Kernel::SharedPtr<Kernel::Process>& proces | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
|  | ||||
| ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||||
|                                            u64& offset, u64& size) { | ||||
| ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|     if (!file.IsOpen()) | ||||
|         return ResultStatus::Error; | ||||
|  | ||||
| @@ -307,12 +306,12 @@ ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& ro | ||||
|         LOG_DEBUG(Loader, "RomFS size:             {:#010X}", romfs_size); | ||||
|  | ||||
|         // 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 ResultStatus::Error; | ||||
|  | ||||
|         offset = romfs_offset; | ||||
|         size = romfs_size; | ||||
|         romfs_file = std::make_shared<FileSys::RomFSReader>(std::move(romfs_file_inner), | ||||
|                                                             romfs_offset, romfs_size); | ||||
|  | ||||
|         return ResultStatus::Success; | ||||
|     } | ||||
|   | ||||
| @@ -35,8 +35,7 @@ public: | ||||
|  | ||||
|     ResultStatus ReadIcon(std::vector<u8>& buffer) override; | ||||
|  | ||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                            u64& size) override; | ||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; | ||||
|  | ||||
| private: | ||||
|     std::string filename; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include <boost/optional.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "common/file_util.h" | ||||
| #include "core/file_sys/romfs_reader.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
|  | ||||
| namespace Kernel { | ||||
| @@ -160,12 +161,9 @@ public: | ||||
|      * Get the RomFS of the application | ||||
|      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||||
|      * @param romfs_file The file containing the RomFS | ||||
|      * @param offset The offset the romfs begins on | ||||
|      * @param size The size of the romfs | ||||
|      * @return ResultStatus result of function | ||||
|      */ | ||||
|     virtual ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                                    u64& size) { | ||||
|     virtual ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|         return ResultStatus::ErrorNotImplemented; | ||||
|     } | ||||
|  | ||||
| @@ -173,12 +171,9 @@ public: | ||||
|      * Get the update RomFS of the application | ||||
|      * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||||
|      * @param romfs_file The file containing the RomFS | ||||
|      * @param offset The offset the romfs begins on | ||||
|      * @param size The size of the romfs | ||||
|      * @return ResultStatus result of function | ||||
|      */ | ||||
|     virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                                          u64& size) { | ||||
|     virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|         return ResultStatus::ErrorNotImplemented; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -211,17 +211,15 @@ ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
|  | ||||
| ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                                        u64& size) { | ||||
|     return base_ncch.ReadRomFS(romfs_file, offset, size); | ||||
| ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|     return base_ncch.ReadRomFS(romfs_file); | ||||
| } | ||||
|  | ||||
| ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||||
|                                              u64& offset, u64& size) { | ||||
|     ResultStatus result = update_ncch.ReadRomFS(romfs_file, offset, size); | ||||
| ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) { | ||||
|     ResultStatus result = update_ncch.ReadRomFS(romfs_file); | ||||
|  | ||||
|     if (result != ResultStatus::Success) | ||||
|         return base_ncch.ReadRomFS(romfs_file, offset, size); | ||||
|         return base_ncch.ReadRomFS(romfs_file); | ||||
|  | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
|   | ||||
| @@ -51,11 +51,9 @@ public: | ||||
|  | ||||
|     ResultStatus ReadProgramId(u64& out_program_id) override; | ||||
|  | ||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                            u64& size) override; | ||||
|     ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; | ||||
|  | ||||
|     ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||||
|                                  u64& size) override; | ||||
|     ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override; | ||||
|  | ||||
|     ResultStatus ReadTitle(std::string& title) override; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 wwylele
					wwylele