diff options
-rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/core/file_sys/partition_filesystem.cpp | 125 | ||||
-rw-r--r-- | src/core/file_sys/partition_filesystem.h | 87 |
3 files changed, 214 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9877b83fe..c1a645460 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,6 +12,8 @@ add_library(core STATIC file_sys/errors.h file_sys/filesystem.cpp file_sys/filesystem.h + file_sys/partition_filesystem.cpp + file_sys/partition_filesystem.h file_sys/path_parser.cpp file_sys/path_parser.h file_sys/program_metadata.cpp diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp new file mode 100644 index 000000000..4a58a9291 --- /dev/null +++ b/src/core/file_sys/partition_filesystem.cpp @@ -0,0 +1,125 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cinttypes> +#include <utility> +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/file_sys/partition_filesystem.h" +#include "core/loader/loader.h" + +namespace FileSys { + +Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) { + FileUtil::IOFile file(file_path, "rb"); + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + // At least be as large as the header + if (file.GetSize() < sizeof(Header)) + return Loader::ResultStatus::Error; + + // For cartridges, HFSs can get very large, so we need to calculate the size up to + // the actual content itself instead of just blindly reading in the entire file. + Header pfs_header; + if (!file.ReadBytes(&pfs_header, sizeof(Header))) + return Loader::ResultStatus::Error; + + bool is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + size_t metadata_size = + sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; + + // Actually read in now... + file.Seek(offset, SEEK_SET); + std::vector<u8> file_data(metadata_size); + + if (!file.ReadBytes(file_data.data(), metadata_size)) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(file_data); + if (result != Loader::ResultStatus::Success) + LOG_ERROR(Service_FS, "Failed to load PFS from file %s!", file_path.c_str()); + + return result; +} + +Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) { + size_t total_size = file_data.size() - offset; + if (total_size < sizeof(Header)) + return Loader::ResultStatus::Error; + + memcpy(&pfs_header, &file_data[offset], sizeof(Header)); + is_hfs = (memcmp(pfs_header.magic.data(), "HFS", 3) == 0); + + size_t entries_offset = offset + sizeof(Header); + size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); + for (u16 i = 0; i < pfs_header.num_entries; i++) { + FileEntry entry; + + memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry)); + entry.name = std::string(reinterpret_cast<const char*>( + &file_data[strtab_offset + entry.fs_entry.strtab_offset])); + pfs_entries.push_back(std::move(entry)); + } + + content_offset = strtab_offset + pfs_header.strtab_size; + + return Loader::ResultStatus::Success; +} + +u32 PartitionFilesystem::GetNumEntries() const { + return pfs_header.num_entries; +} + +u64 PartitionFilesystem::GetEntryOffset(int index) const { + if (index > GetNumEntries()) + return 0; + + return content_offset + pfs_entries[index].fs_entry.offset; +} + +u64 PartitionFilesystem::GetEntrySize(int index) const { + if (index > GetNumEntries()) + return 0; + + return pfs_entries[index].fs_entry.size; +} + +std::string PartitionFilesystem::GetEntryName(int index) const { + if (index > GetNumEntries()) + return ""; + + return pfs_entries[index].name; +} + +u64 PartitionFilesystem::GetFileOffset(const std::string& name) const { + for (u32 i = 0; i < pfs_header.num_entries; i++) { + if (pfs_entries[i].name == name) + return content_offset + pfs_entries[i].fs_entry.offset; + } + + return 0; +} + +u64 PartitionFilesystem::GetFileSize(const std::string& name) const { + for (u32 i = 0; i < pfs_header.num_entries; i++) { + if (pfs_entries[i].name == name) + return pfs_entries[i].fs_entry.size; + } + + return 0; +} + +void PartitionFilesystem::Print() const { + NGLOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic.data()); + NGLOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries); + for (u32 i = 0; i < pfs_header.num_entries; i++) { + NGLOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i, + pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size, + GetFileOffset(pfs_entries[i].name)); + } +} +} // namespace FileSys diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h new file mode 100644 index 000000000..573c90057 --- /dev/null +++ b/src/core/file_sys/partition_filesystem.h @@ -0,0 +1,87 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Loader { +enum class ResultStatus; +} + +namespace FileSys { + +/** + * Helper which implements an interface to parse PFS/HFS filesystems. + * Data can either be loaded from a file path or data with an offset into it. + */ +class PartitionFilesystem { +public: + Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0); + Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0); + + u32 GetNumEntries() const; + u64 GetEntryOffset(int index) const; + u64 GetEntrySize(int index) const; + std::string GetEntryName(int index) const; + u64 GetFileOffset(const std::string& name) const; + u64 GetFileSize(const std::string& name) const; + + void Print() const; + +private: + struct Header { + std::array<char, 4> magic; + u32_le num_entries; + u32_le strtab_size; + INSERT_PADDING_BYTES(0x4); + }; + + static_assert(sizeof(Header) == 0x10, "PFS/HFS header structure size is wrong"); + +#pragma pack(push, 1) + struct FSEntry { + u64_le offset; + u64_le size; + u32_le strtab_offset; + }; + + static_assert(sizeof(FSEntry) == 0x14, "FS entry structure size is wrong"); + + struct PFSEntry { + FSEntry fs_entry; + INSERT_PADDING_BYTES(0x4); + }; + + static_assert(sizeof(PFSEntry) == 0x18, "PFS entry structure size is wrong"); + + struct HFSEntry { + FSEntry fs_entry; + u32_le hash_region_size; + INSERT_PADDING_BYTES(0x8); + std::array<char, 0x20> hash; + }; + + static_assert(sizeof(HFSEntry) == 0x40, "HFS entry structure size is wrong"); + +#pragma pack(pop) + + struct FileEntry { + FSEntry fs_entry; + std::string name; + }; + + Header pfs_header; + bool is_hfs; + size_t content_offset; + + std::vector<FileEntry> pfs_entries; +}; + +} // namespace FileSys |