diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ec011787e..1bc536075 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,8 @@ add_library(core STATIC file_sys/filesystem.h file_sys/path_parser.cpp file_sys/path_parser.h + file_sys/program_metadata.cpp + file_sys/program_metadata.h file_sys/romfs_factory.cpp file_sys/romfs_factory.h file_sys/romfs_filesystem.cpp diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp new file mode 100644 index 000000000..a6dcebcc3 --- /dev/null +++ b/src/core/file_sys/program_metadata.cpp @@ -0,0 +1,114 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/file_sys/program_metadata.h" +#include "core/loader/loader.h" + +namespace FileSys { + +Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) { + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + std::vector file_data(file.GetSize()); + + if (!file.ReadBytes(file_data.data(), file_data.size())) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(file_data); + if (result != Loader::ResultStatus::Success) + LOG_ERROR(Service_FS, "Failed to load NPDM from file %s!", file_path.c_str()); + + return result; +} + +Loader::ResultStatus ProgramMetadata::Load(const std::vector file_data, size_t offset) { + size_t total_size = static_cast(file_data.size() - offset); + if (total_size < sizeof(Header)) + return Loader::ResultStatus::Error; + + size_t header_offset = offset; + memcpy(&npdm_header, &file_data[offset], sizeof(Header)); + + size_t aci_offset = header_offset + npdm_header.aci_offset; + size_t acid_offset = header_offset + npdm_header.acid_offset; + memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader)); + memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader)); + + size_t fac_offset = acid_offset + acid_header.fac_offset; + size_t fah_offset = aci_offset + aci_header.fah_offset; + memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl)); + memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader)); + + return Loader::ResultStatus::Success; +} + +bool ProgramMetadata::Is64BitProgram() const { + return npdm_header.has_64_bit_instructions; +} + +ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const { + return npdm_header.address_space_type; +} + +u8 ProgramMetadata::GetMainThreadPriority() const { + return npdm_header.main_thread_priority; +} + +u8 ProgramMetadata::GetMainThreadCore() const { + return npdm_header.main_thread_cpu; +} + +u32 ProgramMetadata::GetMainThreadStackSize() const { + return npdm_header.main_stack_size; +} + +u64 ProgramMetadata::GetTitleID() const { + return aci_header.title_id; +} + +u64 ProgramMetadata::GetFilesystemPermissions() const { + return aci_file_access.permissions; +} + +void ProgramMetadata::Print() const { + LOG_DEBUG(Service_FS, "Magic: %.4s", npdm_header.magic.data()); + LOG_DEBUG(Service_FS, "Main thread priority: 0x%02x", npdm_header.main_thread_priority); + LOG_DEBUG(Service_FS, "Main thread core: %u", npdm_header.main_thread_cpu); + LOG_DEBUG(Service_FS, "Main thread stack size: 0x%x bytes", npdm_header.main_stack_size); + LOG_DEBUG(Service_FS, "Process category: %u", npdm_header.process_category); + LOG_DEBUG(Service_FS, "Flags: %02x", npdm_header.flags); + LOG_DEBUG(Service_FS, " > 64-bit instructions: %s", + npdm_header.has_64_bit_instructions ? "YES" : "NO"); + + auto address_space = "Unknown"; + switch (npdm_header.address_space_type) { + case ProgramAddressSpaceType::Is64Bit: + address_space = "64-bit"; + break; + case ProgramAddressSpaceType::Is32Bit: + address_space = "32-bit"; + break; + } + + LOG_DEBUG(Service_FS, " > Address space: %s\n", address_space); + + // Begin ACID printing (potential perms, signed) + LOG_DEBUG(Service_FS, "Magic: %.4s", acid_header.magic.data()); + LOG_DEBUG(Service_FS, "Flags: %02x", acid_header.flags); + LOG_DEBUG(Service_FS, " > Is Retail: %s", acid_header.is_retail ? "YES" : "NO"); + LOG_DEBUG(Service_FS, "Title ID Min: %016" PRIX64, acid_header.title_id_min); + LOG_DEBUG(Service_FS, "Title ID Max: %016" PRIX64, acid_header.title_id_max); + LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", acid_file_access.permissions); + + // Begin ACI0 printing (actual perms, unsigned) + LOG_DEBUG(Service_FS, "Magic: %.4s", aci_header.magic.data()); + LOG_DEBUG(Service_FS, "Title ID: %016" PRIX64, aci_header.title_id); + LOG_DEBUG(Service_FS, "Filesystem Access: %016" PRIX64 "\n", aci_file_access.permissions); +} +} // namespace FileSys diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h new file mode 100644 index 000000000..b80a08485 --- /dev/null +++ b/src/core/file_sys/program_metadata.h @@ -0,0 +1,154 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { +enum class ResultStatus; +} + +namespace FileSys { + +enum class ProgramAddressSpaceType : u8 { + Is64Bit = 1, + Is32Bit = 2, +}; + +enum class ProgramFilePermission : u64 { + MountContent = 1ULL << 0, + SaveDataBackup = 1ULL << 5, + SdCard = 1ULL << 21, + Calibration = 1ULL << 34, + Bit62 = 1ULL << 62, + Everything = 1ULL << 63, +}; + +/** + * Helper which implements an interface to parse Program Description Metadata (NPDM) + * Data can either be loaded from a file path or with data and an offset into it. + */ +class ProgramMetadata { +public: + Loader::ResultStatus Load(const std::string& file_path); + Loader::ResultStatus Load(const std::vector file_data, size_t offset = 0); + + bool Is64BitProgram() const; + ProgramAddressSpaceType GetAddressSpaceType() const; + u8 GetMainThreadPriority() const; + u8 GetMainThreadCore() const; + u32 GetMainThreadStackSize() const; + u64 GetTitleID() const; + u64 GetFilesystemPermissions() const; + + void Print() const; + +private: + struct Header { + std::array magic; + std::array reserved; + union { + u8 flags; + + BitField<0, 1, u8> has_64_bit_instructions; + BitField<1, 3, ProgramAddressSpaceType> address_space_type; + BitField<4, 4, u8> reserved_2; + }; + u8 reserved_3; + u8 main_thread_priority; + u8 main_thread_cpu; + std::array reserved_4; + u32_le process_category; + u32_le main_stack_size; + std::array application_name; + std::array reserved_5; + u32_le aci_offset; + u32_le aci_size; + u32_le acid_offset; + u32_le acid_size; + }; + + static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); + + struct AcidHeader { + std::array signature; + std::array nca_modulus; + std::array magic; + u32_le nca_size; + std::array reserved; + union { + u32 flags; + + BitField<0, 1, u32> is_retail; + BitField<1, 31, u32> flags_unk; + }; + u64_le title_id_min; + u64_le title_id_max; + u32_le fac_offset; + u32_le fac_size; + u32_le sac_offset; + u32_le sac_size; + u32_le kac_offset; + u32_le kac_size; + INSERT_PADDING_BYTES(0x8); + }; + + static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong"); + + struct AciHeader { + std::array magic; + std::array reserved; + u64_le title_id; + INSERT_PADDING_BYTES(0x8); + u32_le fah_offset; + u32_le fah_size; + u32_le sac_offset; + u32_le sac_size; + u32_le kac_offset; + u32_le kac_size; + INSERT_PADDING_BYTES(0x8); + }; + + static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong"); + +#pragma pack(push, 1) + + struct FileAccessControl { + u8 version; + INSERT_PADDING_BYTES(3); + u64_le permissions; + std::array unknown; + }; + + static_assert(sizeof(FileAccessControl) == 0x2C, "FS access control structure size is wrong"); + + struct FileAccessHeader { + u8 version; + INSERT_PADDING_BYTES(3); + u64_le permissions; + u32_le unk_offset; + u32_le unk_size; + u32_le unk_offset_2; + u32_le unk_size_2; + }; + + static_assert(sizeof(FileAccessHeader) == 0x1C, "FS access header structure size is wrong"); + +#pragma pack(pop) + + Header npdm_header; + AciHeader aci_header; + AcidHeader acid_header; + + FileAccessControl acid_file_access; + FileAccessHeader aci_file_access; +}; + +} // namespace FileSys diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 661803b5f..864cf25cd 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -53,6 +53,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileUti FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& file, const std::string& filepath) { bool is_main_found{}; + bool is_npdm_found{}; bool is_rtld_found{}; bool is_sdk_found{}; @@ -67,6 +68,9 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& fil // Verify filename if (Common::ToLower(virtual_name) == "main") { is_main_found = true; + } else if (Common::ToLower(virtual_name) == "main.npdm") { + is_npdm_found = true; + return true; } else if (Common::ToLower(virtual_name) == "rtld") { is_rtld_found = true; } else if (Common::ToLower(virtual_name) == "sdk") { @@ -83,14 +87,14 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(FileUtil::IOFile& fil } // We are done if we've found and verified all required NSOs - return !(is_main_found && is_rtld_found && is_sdk_found); + return !(is_main_found && is_npdm_found && is_rtld_found && is_sdk_found); }; // Search the directory recursively, looking for the required modules const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; FileUtil::ForeachDirectoryEntry(nullptr, directory, callback); - if (is_main_found && is_rtld_found && is_sdk_found) { + if (is_main_found && is_npdm_found && is_rtld_found && is_sdk_found) { return FileType::DeconstructedRomDirectory; } @@ -108,14 +112,22 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( process = Kernel::Process::Create("main"); + const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; + const std::string npdm_path = directory + DIR_SEP + "main.npdm"; + + ResultStatus result = metadata.Load(npdm_path); + if (result != ResultStatus::Success) { + return result; + } + metadata.Print(); + // Load NSO modules VAddr next_load_addr{Memory::PROCESS_IMAGE_VADDR}; - const std::string directory = filepath.substr(0, filepath.find_last_of("/\\")) + DIR_SEP; for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { const std::string path = directory + DIR_SEP + module; const VAddr load_addr = next_load_addr; - next_load_addr = AppLoader_NSO::LoadModule(path, load_addr); + next_load_addr = AppLoader_NSO::LoadModule(path, load_addr, metadata.GetTitleID()); if (next_load_addr) { LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, module, load_addr); } else { @@ -127,7 +139,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( process->address_mappings = default_address_mappings; process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Kernel::DEFAULT_STACK_SIZE); + process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), + metadata.GetMainThreadStackSize()); // Find the RomFS by searching for a ".romfs" file in this directory filepath_romfs = FindRomFS(directory); diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 536a2dab7..23295d911 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "core/file_sys/program_metadata.h" #include "core/hle/kernel/kernel.h" #include "core/loader/loader.h" @@ -41,6 +42,7 @@ public: private: std::string filepath_romfs; std::string filepath; + FileSys::ProgramMetadata metadata; }; } // namespace Loader diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 407025da0..7f8d24dd6 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -92,7 +92,7 @@ static constexpr u32 PageAlignSize(u32 size) { return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK; } -VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { +VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base, u64 tid) { FileUtil::IOFile file(path, "rb"); if (!file.IsOpen()) { return {}; @@ -109,7 +109,7 @@ VAddr AppLoader_NSO::LoadModule(const std::string& path, VAddr load_base) { } // Build program image - Kernel::SharedPtr codeset = Kernel::CodeSet::Create("", 0); + Kernel::SharedPtr codeset = Kernel::CodeSet::Create("", tid); std::vector program_image; for (int i = 0; i < nso_header.segments.size(); ++i) { std::vector data = @@ -158,7 +158,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr& process) { process = Kernel::Process::Create("main"); // Load module - LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR); + LoadModule(filepath, Memory::PROCESS_IMAGE_VADDR, 0); LOG_DEBUG(Loader, "loaded module %s @ 0x%" PRIx64, filepath.c_str(), Memory::PROCESS_IMAGE_VADDR); diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 1ae30a824..14eb1d87e 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -29,7 +29,7 @@ public: return IdentifyType(file, filepath); } - static VAddr LoadModule(const std::string& path, VAddr load_base); + static VAddr LoadModule(const std::string& path, VAddr load_base, u64 tid); ResultStatus Load(Kernel::SharedPtr& process) override;