diff options
| author | Zach Hilman <DarkLordZach@users.noreply.github.com> | 2018-06-21 11:16:23 -0400 | 
|---|---|---|
| committer | bunnei <bunneidev@gmail.com> | 2018-06-21 11:16:23 -0400 | 
| commit | 63f26d5c40adb49094b03b232528672f526afe49 (patch) | |
| tree | b76a154e17c819df7803d5860f08406446507a5c /src/core/loader | |
| parent | c3e95086b66dfa4214892dcddb9fd6f865037c1e (diff) | |
Add support for decrypted NCA files (#567)
* Start to add NCA support in loader
* More nca stuff
* More changes to nca.cpp
* Now identifies decrypted NCA cont.
* Game list fixes and more structs and stuff
* More updates to Nca class
* Now reads ExeFs (i think)
* ACTUALLY LOADS EXEFS!
* RomFS loads and games execute
* Cleanup and Finalize
* plumbing, cleanup and testing
* fix some things that i didnt think of before
* Preliminary Review Changes
* Review changes for bunnei and subv
Diffstat (limited to 'src/core/loader')
| -rw-r--r-- | src/core/loader/loader.cpp | 10 | ||||
| -rw-r--r-- | src/core/loader/loader.h | 1 | ||||
| -rw-r--r-- | src/core/loader/nca.cpp | 303 | ||||
| -rw-r--r-- | src/core/loader/nca.h | 49 | ||||
| -rw-r--r-- | src/core/loader/nso.cpp | 79 | ||||
| -rw-r--r-- | src/core/loader/nso.h | 3 | 
6 files changed, 434 insertions, 11 deletions
| diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 6a4fd38cb..20cc0bac0 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -9,6 +9,7 @@  #include "core/hle/kernel/process.h"  #include "core/loader/deconstructed_rom_directory.h"  #include "core/loader/elf.h" +#include "core/loader/nca.h"  #include "core/loader/nro.h"  #include "core/loader/nso.h" @@ -32,6 +33,7 @@ FileType IdentifyFile(FileUtil::IOFile& file, const std::string& filepath) {      CHECK_TYPE(ELF)      CHECK_TYPE(NSO)      CHECK_TYPE(NRO) +    CHECK_TYPE(NCA)  #undef CHECK_TYPE @@ -57,6 +59,8 @@ FileType GuessFromExtension(const std::string& extension_) {          return FileType::NRO;      else if (extension == ".nso")          return FileType::NSO; +    else if (extension == ".nca") +        return FileType::NCA;      return FileType::Unknown;  } @@ -69,6 +73,8 @@ const char* GetFileTypeString(FileType type) {          return "NRO";      case FileType::NSO:          return "NSO"; +    case FileType::NCA: +        return "NCA";      case FileType::DeconstructedRomDirectory:          return "Directory";      case FileType::Error: @@ -104,6 +110,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileTyp      case FileType::NRO:          return std::make_unique<AppLoader_NRO>(std::move(file), filepath); +    // NX NCA file format. +    case FileType::NCA: +        return std::make_unique<AppLoader_NCA>(std::move(file), filepath); +      // NX deconstructed ROM directory.      case FileType::DeconstructedRomDirectory:          return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file), filepath); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index b1aabb1cb..b76f7b13d 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -29,6 +29,7 @@ enum class FileType {      ELF,      NSO,      NRO, +    NCA,      DeconstructedRomDirectory,  }; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp new file mode 100644 index 000000000..067945d46 --- /dev/null +++ b/src/core/loader/nca.cpp @@ -0,0 +1,303 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> + +#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<u8, 0x100> rsa_signature_1; +    std::array<u8, 0x100> 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<u8, 0x10> rights_id; +    std::array<NcaSectionTableEntry, 0x4> section_tables; +    std::array<std::array<u8, 0x20>, 0x4> hash_tables; +    std::array<std::array<u8, 0x10>, 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<u8, 0x20> 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<FileSys::PartitionFilesystem> pfs; +    std::vector<u64> pfs_offset; + +    u64 romfs_offset = 0; +    u64 romfs_size = 0; + +    boost::optional<u8> 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<u8> 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<u8, sizeof(NcaHeader)> 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<u8, sizeof(NcaSectionHeaderBlock)> 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<u64>(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<u8> Nca::GetExeFsFile(const std::string& file_name) { +    std::vector<u8> 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<u8, 0x400> 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<Kernel::Process>& process) { +    if (is_loaded) { +        return ResultStatus::ErrorAlreadyLoaded; +    } +    if (!file.IsOpen()) { +        return ResultStatus::Error; +    } + +    nca = std::make_unique<Nca>(); +    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<FileSys::RomFS_Factory>(*this), +                                                Service::FileSystem::Type::RomFS); + +    is_loaded = true; +    return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, +                                      u64& size) { +    if (nca->GetRomFsSize() == 0) { +        NGLOG_DEBUG(Loader, "No RomFS available"); +        return ResultStatus::ErrorNotUsed; +    } + +    romfs_file = std::make_shared<FileUtil::IOFile>(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 diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h new file mode 100644 index 000000000..3b6c451d0 --- /dev/null +++ b/src/core/loader/nca.h @@ -0,0 +1,49 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/program_metadata.h" +#include "core/hle/kernel/kernel.h" +#include "core/loader/loader.h" + +namespace Loader { + +class Nca; + +/// Loads an NCA file +class AppLoader_NCA final : public AppLoader { +public: +    AppLoader_NCA(FileUtil::IOFile&& file, std::string filepath); + +    /** +     * Returns the type of the file +     * @param file FileUtil::IOFile open file +     * @param filepath Path of the file that we are opening. +     * @return FileType found, or FileType::Error if this loader doesn't know it +     */ +    static FileType IdentifyType(FileUtil::IOFile& file, const std::string& filepath); + +    FileType GetFileType() override { +        return IdentifyType(file, filepath); +    } + +    ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; + +    ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, +                           u64& size) override; + +    ~AppLoader_NCA(); + +private: +    std::string filepath; +    FileSys::ProgramMetadata metadata; + +    std::unique_ptr<Nca> nca; +}; + +} // namespace Loader diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 01be9e217..845ed7e90 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -66,8 +66,22 @@ FileType AppLoader_NSO::IdentifyType(FileUtil::IOFile& file, const std::string&)      return FileType::Error;  } +static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data, +                                         const NsoSegmentHeader& header) { +    std::vector<u8> uncompressed_data; +    uncompressed_data.resize(header.size); +    const int bytes_uncompressed = LZ4_decompress_safe( +        reinterpret_cast<const char*>(compressed_data.data()), +        reinterpret_cast<char*>(uncompressed_data.data()), compressed_data.size(), header.size); + +    ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), +               "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); + +    return uncompressed_data; +} +  static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeader& header, -                                   int compressed_size) { +                                   size_t compressed_size) {      std::vector<u8> compressed_data;      compressed_data.resize(compressed_size); @@ -77,22 +91,65 @@ static std::vector<u8> ReadSegment(FileUtil::IOFile& file, const NsoSegmentHeade          return {};      } -    std::vector<u8> uncompressed_data; -    uncompressed_data.resize(header.size); -    const int bytes_uncompressed = LZ4_decompress_safe( -        reinterpret_cast<const char*>(compressed_data.data()), -        reinterpret_cast<char*>(uncompressed_data.data()), compressed_size, header.size); - -    ASSERT_MSG(bytes_uncompressed == header.size && bytes_uncompressed == uncompressed_data.size(), -               "{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size()); - -    return uncompressed_data; +    return DecompressSegment(compressed_data, header);  }  static constexpr u32 PageAlignSize(u32 size) {      return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;  } +VAddr AppLoader_NSO::LoadModule(const std::string& name, const std::vector<u8>& file_data, +                                VAddr load_base) { +    if (file_data.size() < sizeof(NsoHeader)) +        return {}; + +    NsoHeader nso_header; +    std::memcpy(&nso_header, file_data.data(), sizeof(NsoHeader)); + +    if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) +        return {}; + +    // Build program image +    Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(""); +    std::vector<u8> program_image; +    for (int i = 0; i < nso_header.segments.size(); ++i) { +        std::vector<u8> compressed_data(nso_header.segments_compressed_size[i]); +        for (int j = 0; j < nso_header.segments_compressed_size[i]; ++j) +            compressed_data[j] = file_data[nso_header.segments[i].offset + j]; +        std::vector<u8> data = DecompressSegment(compressed_data, nso_header.segments[i]); +        program_image.resize(nso_header.segments[i].location); +        program_image.insert(program_image.end(), data.begin(), data.end()); +        codeset->segments[i].addr = nso_header.segments[i].location; +        codeset->segments[i].offset = nso_header.segments[i].location; +        codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size())); +    } + +    // MOD header pointer is at .text offset + 4 +    u32 module_offset; +    std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32)); + +    // Read MOD header +    ModHeader mod_header{}; +    // Default .bss to size in segment header if MOD0 section doesn't exist +    u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)}; +    std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader)); +    const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; +    if (has_mod_header) { +        // Resize program image to include .bss section and page align each section +        bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); +    } +    codeset->data.size += bss_size; +    const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)}; +    program_image.resize(image_size); + +    // Load codeset for current process +    codeset->name = name; +    codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); +    Core::CurrentProcess()->LoadModule(codeset, load_base); + +    return load_base + image_size; +} +  VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) {      FileUtil::IOFile file(path, "rb");      if (!file.IsOpen()) { diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 1ae30a824..386f4d39a 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -29,6 +29,9 @@ public:          return IdentifyType(file, filepath);      } +    static VAddr LoadModule(const std::string& name, const std::vector<u8>& file_data, +                            VAddr load_base); +      static VAddr LoadModule(const std::string& path, VAddr load_base);      ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | 
