diff options
Diffstat (limited to 'src/core/file_sys')
-rw-r--r-- | src/core/file_sys/card_image.cpp | 149 | ||||
-rw-r--r-- | src/core/file_sys/card_image.h | 96 | ||||
-rw-r--r-- | src/core/file_sys/content_archive.cpp | 236 | ||||
-rw-r--r-- | src/core/file_sys/content_archive.h | 31 | ||||
-rw-r--r-- | src/core/file_sys/partition_filesystem.cpp | 5 | ||||
-rw-r--r-- | src/core/file_sys/romfs.cpp | 124 | ||||
-rw-r--r-- | src/core/file_sys/romfs.h | 35 | ||||
-rw-r--r-- | src/core/file_sys/vfs.cpp | 43 | ||||
-rw-r--r-- | src/core/file_sys/vfs.h | 23 | ||||
-rw-r--r-- | src/core/file_sys/vfs_offset.cpp | 7 | ||||
-rw-r--r-- | src/core/file_sys/vfs_offset.h | 3 | ||||
-rw-r--r-- | src/core/file_sys/vfs_real.cpp | 6 | ||||
-rw-r--r-- | src/core/file_sys/vfs_real.h | 3 | ||||
-rw-r--r-- | src/core/file_sys/vfs_vector.cpp | 86 | ||||
-rw-r--r-- | src/core/file_sys/vfs_vector.h | 44 |
15 files changed, 827 insertions, 64 deletions
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp new file mode 100644 index 000000000..395eea8ae --- /dev/null +++ b/src/core/file_sys/card_image.cpp @@ -0,0 +1,149 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <array> +#include <string> +#include <core/loader/loader.h> +#include "core/file_sys/card_image.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { + if (file->ReadObject(&header) != sizeof(GamecardHeader)) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } + + if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } + + PartitionFilesystem main_hfs( + std::make_shared<OffsetVfsFile>(file, header.hfs_size, header.hfs_offset)); + + if (main_hfs.GetStatus() != Loader::ResultStatus::Success) { + status = main_hfs.GetStatus(); + return; + } + + static constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", + "logo"}; + + for (XCIPartition partition : + {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { + auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); + if (raw != nullptr) + partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); + } + + auto result = AddNCAFromPartition(XCIPartition::Secure); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + + result = AddNCAFromPartition(XCIPartition::Update); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + + result = AddNCAFromPartition(XCIPartition::Normal); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + + if (GetFormatVersion() >= 0x2) { + result = AddNCAFromPartition(XCIPartition::Logo); + if (result != Loader::ResultStatus::Success) { + status = result; + return; + } + } + + status = Loader::ResultStatus::Success; +} + +Loader::ResultStatus XCI::GetStatus() const { + return status; +} + +VirtualDir XCI::GetPartition(XCIPartition partition) const { + return partitions[static_cast<size_t>(partition)]; +} + +VirtualDir XCI::GetSecurePartition() const { + return GetPartition(XCIPartition::Secure); +} + +VirtualDir XCI::GetNormalPartition() const { + return GetPartition(XCIPartition::Normal); +} + +VirtualDir XCI::GetUpdatePartition() const { + return GetPartition(XCIPartition::Update); +} + +VirtualDir XCI::GetLogoPartition() const { + return GetPartition(XCIPartition::Logo); +} + +std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { + const auto iter = + std::find_if(ncas.begin(), ncas.end(), + [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; }); + return iter == ncas.end() ? nullptr : *iter; +} + +VirtualFile XCI::GetNCAFileByType(NCAContentType type) const { + auto nca = GetNCAByType(type); + if (nca != nullptr) + return nca->GetBaseFile(); + return nullptr; +} + +std::vector<std::shared_ptr<VfsFile>> XCI::GetFiles() const { + return {}; +} + +std::vector<std::shared_ptr<VfsDirectory>> XCI::GetSubdirectories() const { + return std::vector<std::shared_ptr<VfsDirectory>>(); +} + +std::string XCI::GetName() const { + return file->GetName(); +} + +std::shared_ptr<VfsDirectory> XCI::GetParentDirectory() const { + return file->GetContainingDirectory(); +} + +bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} + +Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { + if (partitions[static_cast<size_t>(part)] == nullptr) { + return Loader::ResultStatus::ErrorInvalidFormat; + } + + for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { + if (file->GetExtension() != "nca") + continue; + auto nca = std::make_shared<NCA>(file); + if (nca->GetStatus() == Loader::ResultStatus::Success) + ncas.push_back(std::move(nca)); + } + + return Loader::ResultStatus::Success; +} + +u8 XCI::GetFormatVersion() const { + return GetLogoPartition() == nullptr ? 0x1 : 0x2; +} +} // namespace FileSys diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h new file mode 100644 index 000000000..e089d737c --- /dev/null +++ b/src/core/file_sys/card_image.h @@ -0,0 +1,96 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/vfs.h" +#include "core/loader/loader.h" + +namespace FileSys { + +enum class GamecardSize : u8 { + S_1GB = 0xFA, + S_2GB = 0xF8, + S_4GB = 0xF0, + S_8GB = 0xE0, + S_16GB = 0xE1, + S_32GB = 0xE2, +}; + +struct GamecardInfo { + std::array<u8, 0x70> data; +}; +static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); + +struct GamecardHeader { + std::array<u8, 0x100> signature; + u32_le magic; + u32_le secure_area_start; + u32_le backup_area_start; + u8 kek_index; + GamecardSize size; + u8 header_version; + u8 flags; + u64_le package_id; + u64_le valid_data_end; + u128 info_iv; + u64_le hfs_offset; + u64_le hfs_size; + std::array<u8, 0x20> hfs_header_hash; + std::array<u8, 0x20> initial_data_hash; + u32_le secure_mode_flag; + u32_le title_key_flag; + u32_le key_flag; + u32_le normal_area_end; + GamecardInfo info; +}; +static_assert(sizeof(GamecardHeader) == 0x200, "GamecardHeader has incorrect size."); + +enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; + +class XCI : public ReadOnlyVfsDirectory { +public: + explicit XCI(VirtualFile file); + + Loader::ResultStatus GetStatus() const; + + u8 GetFormatVersion() const; + + VirtualDir GetPartition(XCIPartition partition) const; + VirtualDir GetSecurePartition() const; + VirtualDir GetNormalPartition() const; + VirtualDir GetUpdatePartition() const; + VirtualDir GetLogoPartition() const; + + std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; + VirtualFile GetNCAFileByType(NCAContentType type) const; + + std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; + + std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + + std::string GetName() const override; + + std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + +protected: + bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; + +private: + Loader::ResultStatus AddNCAFromPartition(XCIPartition part); + + VirtualFile file; + GamecardHeader header{}; + + Loader::ResultStatus status; + + std::vector<VirtualDir> partitions; + std::vector<std::shared_ptr<NCA>> ncas; +}; +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index d6b20c047..3529166ac 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -4,9 +4,12 @@ #include <algorithm> #include <utility> - +#include <boost/optional.hpp> #include "common/logging/log.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/ctr_encryption_layer.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/romfs.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" @@ -28,11 +31,19 @@ enum class NCASectionFilesystemType : u8 { struct NCASectionHeaderBlock { INSERT_PADDING_BYTES(3); NCASectionFilesystemType filesystem_type; - u8 crypto_type; + NCASectionCryptoType crypto_type; INSERT_PADDING_BYTES(3); }; static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); +struct NCASectionRaw { + NCASectionHeaderBlock header; + std::array<u8, 0x138> block_data; + std::array<u8, 0x8> section_ctr; + INSERT_PADDING_BYTES(0xB8); +}; +static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); + struct PFS0Superblock { NCASectionHeaderBlock header_block; std::array<u8, 0x20> hash; @@ -42,79 +53,200 @@ struct PFS0Superblock { u64_le hash_table_size; u64_le pfs0_header_offset; u64_le pfs0_size; - INSERT_PADDING_BYTES(432); + INSERT_PADDING_BYTES(0x1B0); }; static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); -struct IVFCLevel { - u64_le offset; - u64_le size; - u32_le block_size; - u32_le reserved; -}; -static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); - struct RomFSSuperblock { NCASectionHeaderBlock header_block; - u32_le magic; - u32_le magic_number; - INSERT_PADDING_BYTES(8); - std::array<IVFCLevel, 6> levels; - INSERT_PADDING_BYTES(64); + IVFCHeader ivfc; + INSERT_PADDING_BYTES(0x118); }; -static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size."); +static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); + +union NCASectionHeader { + NCASectionRaw raw; + PFS0Superblock pfs0; + RomFSSuperblock romfs; +}; +static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); + +bool IsValidNCA(const NCAHeader& header) { + // TODO(DarkLordZach): Add NCA2/NCA0 support. + return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); +} + +u8 NCA::GetCryptoRevision() const { + u8 master_key_id = header.crypto_type; + if (header.crypto_type_2 > master_key_id) + master_key_id = header.crypto_type_2; + if (master_key_id > 0) + --master_key_id; + return master_key_id; +} + +boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { + const auto master_key_id = GetCryptoRevision(); + + if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) + return boost::none; + + std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); + Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( + keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), + Core::Crypto::Mode::ECB); + cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); + + Core::Crypto::Key128 out; + if (type == NCASectionCryptoType::XTS) + std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); + else if (type == NCASectionCryptoType::CTR) + std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); + else + LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", + static_cast<u8>(type)); + u128 out_128{}; + memcpy(out_128.data(), out.data(), 16); + LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", + master_key_id, header.key_index, out_128[1], out_128[0]); + + return out; +} + +boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() const { + const auto master_key_id = GetCryptoRevision(); + + u128 rights_id{}; + memcpy(rights_id.data(), header.rights_id.data(), 16); + if (rights_id == u128{}) + return boost::none; + + auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); + if (titlekey == Core::Crypto::Key128{}) + return boost::none; + Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( + keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); + cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); + + return titlekey; +} + +VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) const { + if (!encrypted) + return in; + + switch (s_header.raw.header.crypto_type) { + case NCASectionCryptoType::NONE: + LOG_DEBUG(Crypto, "called with mode=NONE"); + return in; + case NCASectionCryptoType::CTR: + LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); + { + boost::optional<Core::Crypto::Key128> key = boost::none; + if (std::find_if_not(header.rights_id.begin(), header.rights_id.end(), + [](char c) { return c == 0; }) == header.rights_id.end()) { + key = GetKeyAreaKey(NCASectionCryptoType::CTR); + } else { + key = GetTitlekey(); + } + + if (key == boost::none) + return nullptr; + auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( + std::move(in), key.value(), starting_offset); + std::vector<u8> iv(16); + for (u8 i = 0; i < 8; ++i) + iv[i] = s_header.raw.section_ctr[0x8 - i - 1]; + out->SetIV(iv); + return std::static_pointer_cast<VfsFile>(out); + } + case NCASectionCryptoType::XTS: + // TODO(DarkLordZach): Implement XTSEncryptionLayer. + default: + LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", + static_cast<u8>(s_header.raw.header.crypto_type)); + return nullptr; + } +} NCA::NCA(VirtualFile file_) : file(std::move(file_)) { if (sizeof(NCAHeader) != file->ReadObject(&header)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); + LOG_ERROR(Loader, "File reader errored out during header read."); + + encrypted = false; if (!IsValidNCA(header)) { - status = Loader::ResultStatus::ErrorInvalidFormat; - return; + NCAHeader dec_header{}; + Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, + Core::Crypto::Op::Decrypt); + if (IsValidNCA(dec_header)) { + header = dec_header; + encrypted = true; + } else { + if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) + status = Loader::ResultStatus::ErrorMissingKeys; + else + status = Loader::ResultStatus::ErrorDecrypting; + return; + } } - std::ptrdiff_t number_sections = + const std::ptrdiff_t number_sections = std::count_if(std::begin(header.section_tables), std::end(header.section_tables), [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); + std::vector<NCASectionHeader> sections(number_sections); + const auto length_sections = SECTION_HEADER_SIZE * number_sections; + + if (encrypted) { + auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); + Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, + Core::Crypto::Op::Decrypt); + } else { + file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); + } + for (std::ptrdiff_t i = 0; i < number_sections; ++i) { - // Seek to beginning of this section. - NCASectionHeaderBlock block{}; - if (sizeof(NCASectionHeaderBlock) != - file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); - - if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { - RomFSSuperblock sb{}; - if (sizeof(RomFSSuperblock) != - file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); + auto section = sections[i]; + if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { const size_t romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + - sb.levels[IVFC_MAX_LEVEL - 1].offset; - const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size; - files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset)); - romfs = files.back(); - } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { - PFS0Superblock sb{}; - // Seek back to beginning of this section. - if (sizeof(PFS0Superblock) != - file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) - LOG_CRITICAL(Loader, "File reader errored out during header read."); - + section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; + const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + auto dec = + Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), + romfs_offset); + if (dec != nullptr) { + files.push_back(std::move(dec)); + romfs = files.back(); + } else { + status = Loader::ResultStatus::ErrorMissingKeys; + return; + } + } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * MEDIA_OFFSET_MULTIPLIER) + - sb.pfs0_header_offset; + section.pfs0.pfs0_header_offset; u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - header.section_tables[i].media_offset); - auto npfs = std::make_shared<PartitionFilesystem>( - std::make_shared<OffsetVfsFile>(file, size, offset)); + auto dec = + Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); + if (dec != nullptr) { + auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); - if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.emplace_back(npfs); - if (IsDirectoryExeFS(dirs.back())) - exefs = dirs.back(); + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.push_back(std::move(npfs)); + if (IsDirectoryExeFS(dirs.back())) + exefs = dirs.back(); + } + } else { + status = Loader::ResultStatus::ErrorMissingKeys; + return; } } } @@ -164,6 +296,10 @@ VirtualDir NCA::GetExeFS() const { return exefs; } +VirtualFile NCA::GetBaseFile() const { + return file; +} + bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { return false; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 0b8b9db61..a8879d9a8 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -8,14 +8,18 @@ #include <memory> #include <string> #include <vector> - +#include <boost/optional.hpp> #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/partition_filesystem.h" +#include "core/loader/loader.h" namespace FileSys { +union NCASectionHeader; + enum class NCAContentType : u8 { Program = 0, Meta = 1, @@ -24,6 +28,13 @@ enum class NCAContentType : u8 { Data = 4, }; +enum class NCASectionCryptoType : u8 { + NONE = 1, + XTS = 2, + CTR = 3, + BKTR = 4, +}; + struct NCASectionTableEntry { u32_le media_offset; u32_le media_end_offset; @@ -48,7 +59,7 @@ struct NCAHeader { 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; + std::array<u8, 0x40> key_area; INSERT_PADDING_BYTES(0xC0); }; static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); @@ -58,10 +69,7 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } -inline bool IsValidNCA(const NCAHeader& header) { - return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || - header.magic == Common::MakeMagic('N', 'C', 'A', '3'); -} +bool IsValidNCA(const NCAHeader& header); // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. // After construction, use GetStatus to determine if the file is valid and ready to be used. @@ -81,10 +89,17 @@ public: VirtualFile GetRomFS() const; VirtualDir GetExeFS() const; + VirtualFile GetBaseFile() const; + protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + u8 GetCryptoRevision() const; + boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; + boost::optional<Core::Crypto::Key128> GetTitlekey() const; + VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset) const; + std::vector<VirtualDir> dirs; std::vector<VirtualFile> files; @@ -95,6 +110,10 @@ private: NCAHeader header{}; Loader::ResultStatus status{}; + + bool encrypted; + + Core::Crypto::KeyManager keys; }; } // namespace FileSys diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 521e21078..47e032b19 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -97,9 +97,8 @@ void PartitionFilesystem::PrintDebugInfo() const { LOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic); LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries); for (u32 i = 0; i < pfs_header.num_entries; i++) { - LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i, - pfs_files[i]->GetName(), pfs_files[i]->GetSize(), - dynamic_cast<OffsetVfsFile*>(pfs_files[i].get())->GetOffset()); + LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes)", i, + pfs_files[i]->GetName(), pfs_files[i]->GetSize()); } } diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp new file mode 100644 index 000000000..ff3ddb29c --- /dev/null +++ b/src/core/file_sys/romfs.cpp @@ -0,0 +1,124 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" +#include "common/swap.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; + +struct TableLocation { + u64_le offset; + u64_le size; +}; +static_assert(sizeof(TableLocation) == 0x10, "TableLocation has incorrect size."); + +struct RomFSHeader { + u64_le header_size; + TableLocation directory_hash; + TableLocation directory_meta; + TableLocation file_hash; + TableLocation file_meta; + u64_le data_offset; +}; +static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); + +struct DirectoryEntry { + u32_le sibling; + u32_le child_dir; + u32_le child_file; + u32_le hash; + u32_le name_length; +}; +static_assert(sizeof(DirectoryEntry) == 0x14, "DirectoryEntry has incorrect size."); + +struct FileEntry { + u32_le parent; + u32_le sibling; + u64_le offset; + u64_le size; + u32_le hash; + u32_le name_length; +}; +static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size."); + +template <typename Entry> +static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t offset) { + Entry entry{}; + if (file->ReadObject(&entry, offset) != sizeof(Entry)) + return {}; + std::string string(entry.name_length, '\0'); + if (file->ReadArray(&string[0], string.size(), offset + sizeof(Entry)) != string.size()) + return {}; + return {entry, string}; +} + +void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 this_file_offset, + std::shared_ptr<VectorVfsDirectory> parent) { + while (true) { + auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset); + + parent->AddFile(std::make_shared<OffsetVfsFile>( + file, entry.first.size, entry.first.offset + data_offset, entry.second, parent)); + + if (entry.first.sibling == ROMFS_ENTRY_EMPTY) + break; + + this_file_offset = entry.first.sibling; + } +} + +void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, size_t data_offset, + u32 this_dir_offset, std::shared_ptr<VectorVfsDirectory> parent) { + while (true) { + auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset); + auto current = std::make_shared<VectorVfsDirectory>( + std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second); + + if (entry.first.child_file != ROMFS_ENTRY_EMPTY) { + ProcessFile(file, file_offset, data_offset, entry.first.child_file, current); + } + + if (entry.first.child_dir != ROMFS_ENTRY_EMPTY) { + ProcessDirectory(file, dir_offset, file_offset, data_offset, entry.first.child_dir, + current); + } + + parent->AddDirectory(current); + if (entry.first.sibling == ROMFS_ENTRY_EMPTY) + break; + this_dir_offset = entry.first.sibling; + } +} + +VirtualDir ExtractRomFS(VirtualFile file) { + RomFSHeader header{}; + if (file->ReadObject(&header) != sizeof(RomFSHeader)) + return nullptr; + + if (header.header_size != sizeof(RomFSHeader)) + return nullptr; + + const u64 file_offset = header.file_meta.offset; + const u64 dir_offset = header.directory_meta.offset + 4; + + const auto root = + std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, + file->GetContainingDirectory(), file->GetName()); + + ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root); + + VirtualDir out = std::move(root); + + while (out->GetSubdirectory("") != nullptr) + out = out->GetSubdirectory(""); + + return out; +} +} // namespace FileSys diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h new file mode 100644 index 000000000..03a876d22 --- /dev/null +++ b/src/core/file_sys/romfs.h @@ -0,0 +1,35 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +struct IVFCLevel { + u64_le offset; + u64_le size; + u32_le block_size; + u32_le reserved; +}; +static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); + +struct IVFCHeader { + u32_le magic; + u32_le magic_number; + INSERT_PADDING_BYTES(8); + std::array<IVFCLevel, 6> levels; + INSERT_PADDING_BYTES(64); +}; +static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); + +// Converts a RomFS binary blob to VFS Filesystem +// Returns nullptr on failure +VirtualDir ExtractRomFS(VirtualFile file); + +} // namespace FileSys diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index b99a4fd5b..dae1c16ef 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -46,6 +46,13 @@ size_t VfsFile::WriteBytes(const std::vector<u8>& data, size_t offset) { return Write(data.data(), data.size(), offset); } +std::string VfsFile::GetFullPath() const { + if (GetContainingDirectory() == nullptr) + return "/" + GetName(); + + return GetContainingDirectory()->GetFullPath() + "/" + GetName(); +} + std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) const { auto vec = FileUtil::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), @@ -243,6 +250,13 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) { return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); } +std::string VfsDirectory::GetFullPath() const { + if (IsRoot()) + return GetName(); + + return GetParentDirectory()->GetFullPath() + "/" + GetName(); +} + bool ReadOnlyVfsDirectory::IsWritable() const { return false; } @@ -270,4 +284,33 @@ bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) { bool ReadOnlyVfsDirectory::Rename(std::string_view name) { return false; } + +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { + if (file1->GetSize() != file2->GetSize()) + return false; + + std::vector<u8> f1_v(block_size); + std::vector<u8> f2_v(block_size); + for (size_t i = 0; i < file1->GetSize(); i += block_size) { + auto f1_vs = file1->Read(f1_v.data(), block_size, i); + auto f2_vs = file2->Read(f2_v.data(), block_size, i); + + if (f1_vs != f2_vs) + return false; + auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end()); + if (iters.first != f1_v.end() && iters.second != f2_v.end()) + return false; + } + + return true; +} + +bool VfsRawCopy(VirtualFile src, VirtualFile dest) { + if (src == nullptr || dest == nullptr) + return false; + if (!dest->Resize(src->GetSize())) + return false; + std::vector<u8> data = src->ReadAllBytes(); + return dest->WriteBytes(data, 0) == data.size(); +} } // namespace FileSys diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 4a13b8378..fab9e2b45 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -113,6 +113,9 @@ struct VfsFile : NonCopyable { // Renames the file to name. Returns whether or not the operation was successsful. virtual bool Rename(std::string_view name) = 0; + + // Returns the full path of this file as a string, recursively + virtual std::string GetFullPath() const; }; // A class representing a directory in an abstract filesystem. @@ -213,6 +216,17 @@ struct VfsDirectory : NonCopyable { return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p)); } + bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function, + const std::string& file) { + auto file_p = GetFile(file); + if (file_p == nullptr) + return false; + return ReplaceFileWithSubdirectory(file_p, function(file_p)); + } + + // Returns the full path of this directory as a string, recursively + virtual std::string GetFullPath() const; + protected: // Backend for InterpretAsDirectory. // Removes all references to file and adds a reference to dir in the directory's implementation. @@ -230,4 +244,13 @@ struct ReadOnlyVfsDirectory : public VfsDirectory { bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; }; + +// Compare the two files, byte-for-byte, in increments specificed by block_size +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); + +// A method that copies the raw data between two different implementations of VirtualFile. If you +// are using the same implementation, it is probably better to use the Copy method in the parent +// directory of src/dest. +bool VfsRawCopy(VirtualFile src, VirtualFile dest); + } // namespace FileSys diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index a40331cef..847cde2f5 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -10,8 +10,9 @@ namespace FileSys { OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_, - std::string name_) - : file(std::move(file_)), offset(offset_), size(size_), name(std::move(name_)) {} + std::string name_, VirtualDir parent_) + : file(file_), offset(offset_), size(size_), name(std::move(name_)), + parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} std::string OffsetVfsFile::GetName() const { return name.empty() ? file->GetName() : name; @@ -35,7 +36,7 @@ bool OffsetVfsFile::Resize(size_t new_size) { } std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const { - return file->GetContainingDirectory(); + return parent; } bool OffsetVfsFile::IsWritable() const { diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h index 4f471e3ba..235970dc5 100644 --- a/src/core/file_sys/vfs_offset.h +++ b/src/core/file_sys/vfs_offset.h @@ -17,7 +17,7 @@ namespace FileSys { // the size of this wrapper. struct OffsetVfsFile : public VfsFile { OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0, - std::string new_name = ""); + std::string new_name = "", VirtualDir new_parent = nullptr); std::string GetName() const override; size_t GetSize() const override; @@ -44,6 +44,7 @@ private: size_t offset; size_t size; std::string name; + VirtualDir parent; }; } // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 9ce2e1efa..82d54da4a 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -195,6 +195,12 @@ bool RealVfsDirectory::Rename(std::string_view name) { return FileUtil::Rename(path, new_name); } +std::string RealVfsDirectory::GetFullPath() const { + auto out = path; + std::replace(out.begin(), out.end(), '\\', '/'); + return out; +} + bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { const auto iter = std::find(files.begin(), files.end(), file); if (iter == files.end()) diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 2151211c9..243d58576 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -41,7 +41,7 @@ private: // An implementation of VfsDirectory that represents a directory on the user's computer. struct RealVfsDirectory : public VfsDirectory { - RealVfsDirectory(const std::string& path, Mode perms); + RealVfsDirectory(const std::string& path, Mode perms = Mode::Read); std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; @@ -54,6 +54,7 @@ struct RealVfsDirectory : public VfsDirectory { bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; + std::string GetFullPath() const override; protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp new file mode 100644 index 000000000..fda603960 --- /dev/null +++ b/src/core/file_sys/vfs_vector.cpp @@ -0,0 +1,86 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <utility> +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { +VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, + std::vector<VirtualDir> dirs_, VirtualDir parent_, + std::string name_) + : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), + name(std::move(name_)) {} + +std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const { + return files; +} + +std::vector<std::shared_ptr<VfsDirectory>> VectorVfsDirectory::GetSubdirectories() const { + return dirs; +} + +bool VectorVfsDirectory::IsWritable() const { + return false; +} + +bool VectorVfsDirectory::IsReadable() const { + return true; +} + +std::string VectorVfsDirectory::GetName() const { + return name; +} + +std::shared_ptr<VfsDirectory> VectorVfsDirectory::GetParentDirectory() const { + return parent; +} + +template <typename T> +static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) { + const auto iter = + std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; }); + if (iter == vec.end()) + return false; + + vec.erase(iter); + return true; +} + +bool VectorVfsDirectory::DeleteSubdirectory(std::string_view name) { + return FindAndRemoveVectorElement(dirs, name); +} + +bool VectorVfsDirectory::DeleteFile(std::string_view name) { + return FindAndRemoveVectorElement(files, name); +} + +bool VectorVfsDirectory::Rename(std::string_view name_) { + name = name_; + return true; +} + +std::shared_ptr<VfsDirectory> VectorVfsDirectory::CreateSubdirectory(std::string_view name) { + return nullptr; +} + +std::shared_ptr<VfsFile> VectorVfsDirectory::CreateFile(std::string_view name) { + return nullptr; +} + +void VectorVfsDirectory::AddFile(VirtualFile file) { + files.push_back(std::move(file)); +} + +void VectorVfsDirectory::AddDirectory(VirtualDir dir) { + dirs.push_back(std::move(dir)); +} + +bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + if (!DeleteFile(file->GetName())) + return false; + dirs.emplace_back(std::move(dir)); + return true; +} +} // namespace FileSys diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h new file mode 100644 index 000000000..ba469647b --- /dev/null +++ b/src/core/file_sys/vfs_vector.h @@ -0,0 +1,44 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs.h" + +namespace FileSys { + +// An implementation of VfsDirectory that maintains two vectors for subdirectories and files. +// Vector data is supplied upon construction. +struct VectorVfsDirectory : public VfsDirectory { + explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, + std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr, + std::string name = ""); + + std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; + std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::string GetName() const override; + std::shared_ptr<VfsDirectory> GetParentDirectory() const override; + bool DeleteSubdirectory(std::string_view name) override; + bool DeleteFile(std::string_view name) override; + bool Rename(std::string_view name) override; + std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; + std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; + + virtual void AddFile(VirtualFile file); + virtual void AddDirectory(VirtualDir dir); + +protected: + bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; + +private: + std::vector<VirtualFile> files; + std::vector<VirtualDir> dirs; + + VirtualDir parent; + std::string name; +}; + +} // namespace FileSys |