// Copyright 2018 yuzu emulator team // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/swap.h" #include "core/core.h" #include "core/file_sys/program_metadata.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/nca.h" #include "core/loader/nso.h" #include "core/memory.h" namespace Loader { // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; constexpr u64 SECTION_HEADER_SIZE = 0x200; constexpr u64 SECTION_HEADER_OFFSET = 0x400; enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; struct NcaSectionTableEntry { u32_le media_offset; u32_le media_end_offset; INSERT_PADDING_BYTES(0x8); }; static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size."); struct NcaHeader { std::array rsa_signature_1; std::array rsa_signature_2; u32_le magic; u8 is_system; NcaContentType content_type; u8 crypto_type; u8 key_index; u64_le size; u64_le title_id; INSERT_PADDING_BYTES(0x4); u32_le sdk_version; u8 crypto_type_2; INSERT_PADDING_BYTES(15); std::array rights_id; std::array section_tables; std::array, 0x4> hash_tables; std::array, 0x4> key_area; INSERT_PADDING_BYTES(0xC0); }; static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size."); struct NcaSectionHeaderBlock { INSERT_PADDING_BYTES(3); NcaSectionFilesystemType filesystem_type; u8 crypto_type; INSERT_PADDING_BYTES(3); }; static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size."); struct Pfs0Superblock { NcaSectionHeaderBlock header_block; std::array hash; u32_le size; INSERT_PADDING_BYTES(4); u64_le hash_table_offset; u64_le hash_table_size; u64_le pfs0_header_offset; u64_le pfs0_size; INSERT_PADDING_BYTES(432); }; static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size."); static bool IsValidNca(const NcaHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } // TODO(DarkLordZach): Add support for encrypted. class Nca final { std::vector pfs; std::vector pfs_offset; u64 romfs_offset = 0; u64 romfs_size = 0; boost::optional exefs_id = boost::none; FileUtil::IOFile file; std::string path; u64 GetExeFsFileOffset(const std::string& file_name) const; u64 GetExeFsFileSize(const std::string& file_name) const; public: ResultStatus Load(FileUtil::IOFile&& file, std::string path); FileSys::PartitionFilesystem GetPfs(u8 id) const; u64 GetRomFsOffset() const; u64 GetRomFsSize() const; std::vector GetExeFsFile(const std::string& file_name); }; static bool IsPfsExeFs(const FileSys::PartitionFilesystem& pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs.GetFileSize("main") > 0 && pfs.GetFileSize("main.npdm") > 0; } ResultStatus Nca::Load(FileUtil::IOFile&& in_file, std::string in_path) { file = std::move(in_file); path = in_path; file.Seek(0, SEEK_SET); std::array header_array{}; if (sizeof(NcaHeader) != file.ReadBytes(header_array.data(), sizeof(NcaHeader))) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); NcaHeader header{}; std::memcpy(&header, header_array.data(), sizeof(NcaHeader)); if (!IsValidNca(header)) return ResultStatus::ErrorInvalidFormat; int number_sections = std::count_if(std::begin(header.section_tables), std::end(header.section_tables), [](NcaSectionTableEntry entry) { return entry.media_offset > 0; }); for (int i = 0; i < number_sections; ++i) { // Seek to beginning of this section. file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); std::array array{}; if (sizeof(NcaSectionHeaderBlock) != file.ReadBytes(array.data(), sizeof(NcaSectionHeaderBlock))) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); NcaSectionHeaderBlock block{}; std::memcpy(&block, array.data(), sizeof(NcaSectionHeaderBlock)); if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) { romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; romfs_size = header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset; } else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) { Pfs0Superblock sb{}; // Seek back to beginning of this section. file.Seek(SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE, SEEK_SET); if (sizeof(Pfs0Superblock) != file.ReadBytes(&sb, sizeof(Pfs0Superblock))) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); u64 offset = (static_cast(header.section_tables[i].media_offset) * MEDIA_OFFSET_MULTIPLIER) + sb.pfs0_header_offset; FileSys::PartitionFilesystem npfs{}; ResultStatus status = npfs.Load(path, offset); if (status == ResultStatus::Success) { pfs.emplace_back(std::move(npfs)); pfs_offset.emplace_back(offset); } } } for (size_t i = 0; i < pfs.size(); ++i) { if (IsPfsExeFs(pfs[i])) exefs_id = i; } return ResultStatus::Success; } FileSys::PartitionFilesystem Nca::GetPfs(u8 id) const { return pfs[id]; } u64 Nca::GetExeFsFileOffset(const std::string& file_name) const { if (exefs_id == boost::none) return 0; return pfs[*exefs_id].GetFileOffset(file_name) + pfs_offset[*exefs_id]; } u64 Nca::GetExeFsFileSize(const std::string& file_name) const { if (exefs_id == boost::none) return 0; return pfs[*exefs_id].GetFileSize(file_name); } u64 Nca::GetRomFsOffset() const { return romfs_offset; } u64 Nca::GetRomFsSize() const { return romfs_size; } std::vector Nca::GetExeFsFile(const std::string& file_name) { std::vector out(GetExeFsFileSize(file_name)); file.Seek(GetExeFsFileOffset(file_name), SEEK_SET); file.ReadBytes(out.data(), GetExeFsFileSize(file_name)); return out; } AppLoader_NCA::AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath) : AppLoader(std::move(file)), filepath(std::move(filepath)) {} FileType AppLoader_NCA::IdentifyType(FileUtil::IOFile& file, const std::string&) { file.Seek(0, SEEK_SET); std::array header_enc_array{}; if (0x400 != file.ReadBytes(header_enc_array.data(), 0x400)) return FileType::Error; // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. NcaHeader header{}; std::memcpy(&header, header_enc_array.data(), sizeof(NcaHeader)); if (IsValidNca(header) && header.content_type == NcaContentType::Program) return FileType::NCA; return FileType::Error; } ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr& process) { if (is_loaded) { return ResultStatus::ErrorAlreadyLoaded; } if (!file.IsOpen()) { return ResultStatus::Error; } nca = std::make_unique(); ResultStatus result = nca->Load(std::move(file), filepath); if (result != ResultStatus::Success) { return result; } result = metadata.Load(nca->GetExeFsFile("main.npdm")); if (result != ResultStatus::Success) { return result; } metadata.Print(); const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; if (arch_bits == FileSys::ProgramAddressSpaceType::Is32Bit) { return ResultStatus::ErrorUnsupportedArch; } VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { const VAddr load_addr = next_load_addr; next_load_addr = AppLoader_NSO::LoadModule(module, nca->GetExeFsFile(module), load_addr); if (next_load_addr) { NGLOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr); } else { next_load_addr = load_addr; } } process->program_id = metadata.GetTitleID(); process->svc_access_mask.set(); process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()); if (nca->GetRomFsSize() > 0) Service::FileSystem::RegisterFileSystem(std::make_unique(*this), Service::FileSystem::Type::RomFS); is_loaded = true; return ResultStatus::Success; } ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) { if (nca->GetRomFsSize() == 0) { NGLOG_DEBUG(Loader, "No RomFS available"); return ResultStatus::ErrorNotUsed; } romfs_file = std::make_shared(filepath, "rb"); offset = nca->GetRomFsOffset(); size = nca->GetRomFsSize(); NGLOG_DEBUG(Loader, "RomFS offset: 0x{:016X}", offset); NGLOG_DEBUG(Loader, "RomFS size: 0x{:016X}", size); return ResultStatus::Success; } AppLoader_NCA::~AppLoader_NCA() = default; } // namespace Loader