diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp index 85f7f8ce38..4151b7bc7f 100644 --- a/Source/Core/DiscIO/WIABlob.cpp +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -4,6 +4,8 @@ #include "DiscIO/WIABlob.h" +#include +#include #include #include "Common/CommonTypes.h" @@ -46,16 +48,92 @@ bool WIAFileReader::Initialize(const std::string& path) return false; } - if (Common::swap32(m_header_1.header_2_size) < sizeof(WIAHeader2)) + const u32 header_2_size = Common::swap32(m_header_1.header_2_size); + const u32 header_2_min_size = sizeof(WIAHeader2) - sizeof(WIAHeader2::compressor_data); + if (header_2_size < header_2_min_size) return false; - if (!m_file.ReadArray(&m_header_2, 1)) + std::vector header_2(header_2_size); + if (!m_file.ReadBytes(header_2.data(), header_2.size())) return false; + std::memcpy(&m_header_2, header_2.data(), std::min(header_2.size(), sizeof(WIAHeader2))); + + if (m_header_2.compressor_data_size > sizeof(WIAHeader2::compressor_data) || + header_2_size < header_2_min_size + m_header_2.compressor_data_size) + { + return false; + } + const u32 chunk_size = Common::swap32(m_header_2.chunk_size); if (chunk_size % VolumeWii::GROUP_TOTAL_SIZE != 0) return false; + const u32 compression_type = Common::swap32(m_header_2.compression_type); + if (compression_type != 0) + { + ERROR_LOG(DISCIO, "Unsupported WIA compression type %u in %s", compression_type, path.c_str()); + return false; + } + + const size_t number_of_partition_entries = Common::swap32(m_header_2.number_of_partition_entries); + const size_t partition_entry_size = Common::swap32(m_header_2.partition_entry_size); + std::vector partition_entries(partition_entry_size * number_of_partition_entries); + if (!m_file.Seek(Common::swap64(m_header_2.partition_entries_offset), SEEK_SET)) + return false; + if (!m_file.ReadBytes(partition_entries.data(), partition_entries.size())) + return false; + // TODO: Check hash + + const size_t copy_length = std::min(partition_entry_size, sizeof(PartitionEntry)); + const size_t memset_length = sizeof(PartitionEntry) - copy_length; + u8* ptr = partition_entries.data(); + m_partition_entries.resize(number_of_partition_entries); + for (size_t i = 0; i < number_of_partition_entries; ++i, ptr += partition_entry_size) + { + std::memcpy(&m_partition_entries[i], ptr, copy_length); + std::memset(reinterpret_cast(&m_partition_entries[i]) + copy_length, 0, memset_length); + } + + for (const PartitionEntry& partition : m_partition_entries) + { + if (Common::swap32(partition.data_entries[1].number_of_sectors) != 0) + { + const u32 first_end = Common::swap32(partition.data_entries[0].first_sector) + + Common::swap32(partition.data_entries[0].number_of_sectors); + const u32 second_start = Common::swap32(partition.data_entries[1].first_sector); + if (first_end > second_start) + return false; + } + } + + std::sort(m_partition_entries.begin(), m_partition_entries.end(), + [](const PartitionEntry& a, const PartitionEntry& b) { + return Common::swap32(a.data_entries[0].first_sector) < + Common::swap32(b.data_entries[0].first_sector); + }); + + // TODO: Compression + const u32 number_of_raw_data_entries = Common::swap32(m_header_2.number_of_raw_data_entries); + m_raw_data_entries.resize(number_of_raw_data_entries); + if (!m_file.Seek(Common::swap64(m_header_2.raw_data_entries_offset), SEEK_SET)) + return false; + if (!m_file.ReadArray(m_raw_data_entries.data(), number_of_raw_data_entries)) + return false; + + std::sort(m_raw_data_entries.begin(), m_raw_data_entries.end(), + [](const RawDataEntry& a, const RawDataEntry& b) { + return Common::swap64(a.data_offset) < Common::swap64(b.data_offset); + }); + + // TODO: Compression + const u32 number_of_group_entries = Common::swap32(m_header_2.number_of_group_entries); + m_group_entries.resize(number_of_group_entries); + if (!m_file.Seek(Common::swap64(m_header_2.group_entries_offset), SEEK_SET)) + return false; + if (!m_file.ReadArray(m_group_entries.data(), number_of_group_entries)) + return false; + return true; } @@ -67,14 +145,64 @@ std::unique_ptr WIAFileReader::Create(File::IOFile file, const st bool WIAFileReader::Read(u64 offset, u64 size, u8* out_ptr) { - if (offset + size <= sizeof(WIAHeader2::disc_header)) + if (offset + size > Common::swap64(m_header_1.iso_file_size)) + return false; + + if (offset < sizeof(WIAHeader2::disc_header)) { - std::memcpy(out_ptr, m_header_2.disc_header.data() + offset, size); - return true; + const u64 bytes_to_read = std::min(sizeof(WIAHeader2::disc_header) - offset, size); + std::memcpy(out_ptr, m_header_2.disc_header.data() + offset, bytes_to_read); + offset += bytes_to_read; + size -= bytes_to_read; + out_ptr += bytes_to_read; } - // Not implemented - return false; + const u32 chunk_size = Common::swap32(m_header_2.chunk_size); + for (RawDataEntry raw_data : m_raw_data_entries) + { + if (size == 0) + return true; + + u64 data_offset = Common::swap64(raw_data.data_offset); + u64 data_size = Common::swap64(raw_data.data_size); + + if (data_offset + data_size <= offset) + continue; + + if (offset < data_offset) + return false; + + const u64 skipped_data = data_offset % VolumeWii::BLOCK_TOTAL_SIZE; + data_offset -= skipped_data; + data_size += skipped_data; + + const u32 number_of_groups = Common::swap32(raw_data.number_of_groups); + const u64 group_index = (offset - data_offset) / chunk_size; + for (u64 i = group_index; i < number_of_groups && size > 0; ++i) + { + const u64 total_group_index = Common::swap32(raw_data.group_index) + i; + if (total_group_index >= m_group_entries.size()) + return false; + + const GroupEntry group = m_group_entries[total_group_index]; + const u64 group_offset_on_disc = data_offset + i * chunk_size; + const u64 offset_in_group = offset - group_offset_on_disc; + + // TODO: Compression + + const u64 group_offset_in_file = static_cast(Common::swap32(group.data_offset)) << 2; + const u64 offset_in_file = group_offset_in_file + offset_in_group; + const u64 bytes_to_read = std::min(chunk_size - offset_in_group, size); + if (!m_file.Seek(offset_in_file, SEEK_SET) || !m_file.ReadBytes(out_ptr, bytes_to_read)) + return false; + + offset += bytes_to_read; + size -= bytes_to_read; + out_ptr += bytes_to_read; + } + } + + return size == 0; } std::string WIAFileReader::VersionToString(u32 version) diff --git a/Source/Core/DiscIO/WIABlob.h b/Source/Core/DiscIO/WIABlob.h index edd7d0b776..c8ec442488 100644 --- a/Source/Core/DiscIO/WIABlob.h +++ b/Source/Core/DiscIO/WIABlob.h @@ -41,6 +41,7 @@ private: static std::string VersionToString(u32 version); using SHA1 = std::array; + using WiiKey = std::array; #pragma pack(push, 1) struct WIAHeader1 @@ -54,10 +55,8 @@ private: u64 wia_file_size; SHA1 header_1_hash; }; -#pragma pack(pop) static_assert(sizeof(WIAHeader1) == 0x48, "Wrong size for WIA header 1"); -#pragma pack(push, 1) struct WIAHeader2 { u32 disc_type; @@ -67,7 +66,7 @@ private: std::array disc_header; - u32 number_of_partitions_entries; + u32 number_of_partition_entries; u32 partition_entry_size; u64 partition_entries_offset; SHA1 partition_entries_hash; @@ -79,9 +78,51 @@ private: u32 number_of_group_entries; u64 group_entries_offset; u32 group_entries_size; + + u8 compressor_data_size; + u8 compressor_data[7]; }; + static_assert(sizeof(WIAHeader2) == 0xdc, "Wrong size for WIA header 2"); + + struct PartitionDataEntry + { + u32 first_sector; + u32 number_of_sectors; + u32 group_index; + u32 number_of_groups; + }; + static_assert(sizeof(PartitionDataEntry) == 0x10, "Wrong size for WIA partition data entry"); + + struct PartitionEntry + { + WiiKey partition_key; + std::array data_entries; + }; + static_assert(sizeof(PartitionEntry) == 0x30, "Wrong size for WIA partition entry"); + + struct RawDataEntry + { + u64 data_offset; + u64 data_size; + u32 group_index; + u32 number_of_groups; + }; + static_assert(sizeof(RawDataEntry) == 0x18, "Wrong size for WIA raw data entry"); + + struct GroupEntry + { + u32 data_offset; // >> 2 + u32 data_size; + }; + static_assert(sizeof(GroupEntry) == 0x08, "Wrong size for WIA group entry"); + + struct HashExceptionEntry + { + u16 offset; + SHA1 hash; + }; + static_assert(sizeof(HashExceptionEntry) == 0x16, "Wrong size for WIA hash exception entry"); #pragma pack(pop) - static_assert(sizeof(WIAHeader2) == 0xd4, "Wrong size for WIA header 2"); bool m_valid; @@ -89,6 +130,9 @@ private: WIAHeader1 m_header_1; WIAHeader2 m_header_2; + std::vector m_partition_entries; + std::vector m_raw_data_entries; + std::vector m_group_entries; static constexpr u32 WIA_VERSION = 0x01000000; static constexpr u32 WIA_VERSION_WRITE_COMPATIBLE = 0x01000000;