From 2b28fd78094ce754d06b81ed50bf3fda5352feb1 Mon Sep 17 00:00:00 2001 From: shinyquagsire23 Date: Sun, 25 Feb 2018 03:34:19 -0700 Subject: [PATCH] file_sys: Add support for parsing NPDM files --- src/core/CMakeLists.txt | 2 + src/core/file_sys/program_metadata.cpp | 117 ++++++++++++++++++ src/core/file_sys/program_metadata.h | 157 +++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 src/core/file_sys/program_metadata.cpp create mode 100644 src/core/file_sys/program_metadata.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ec011787eb..1bc536075c 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 0000000000..041617bb59 --- /dev/null +++ b/src/core/file_sys/program_metadata.cpp @@ -0,0 +1,117 @@ +// 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" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +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 0000000000..4e04a8e68f --- /dev/null +++ b/src/core/file_sys/program_metadata.h @@ -0,0 +1,157 @@ +// 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; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +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; + std::array padding; + }; + + static_assert(sizeof(AcidHeader) == 0x240, "ACID header structure size is wrong"); + + struct AciHeader { + std::array magic; + std::array reserved; + u64_le title_id; + std::array padding; + u32_le fah_offset; + u32_le fah_size; + u32_le sac_offset; + u32_le sac_size; + u32_le kac_offset; + u32_le kac_size; + std::array padding_2; + }; + + static_assert(sizeof(AciHeader) == 0x40, "ACI0 header structure size is wrong"); + +#pragma pack(push, 1) + + struct FileAccessControl { + u8 version; + std::array padding; + u64_le permissions; + std::array unknown; + }; + + static_assert(sizeof(FileAccessControl) == 0x2C, "FS access control structure size is wrong"); + + struct FileAccessHeader { + u8 version; + std::array padding; + 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