WIA: Implement PURGE decompression

This commit is contained in:
JosJuice 2019-12-29 23:29:51 +01:00
parent 3672bd79f3
commit 36991e2dde
2 changed files with 171 additions and 25 deletions

View File

@ -5,8 +5,10 @@
#include "DiscIO/WIABlob.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <utility>
#include "Common/Align.h"
#include "Common/CommonTypes.h"
@ -71,7 +73,8 @@ bool WIAFileReader::Initialize(const std::string& path)
return false;
const u32 compression_type = Common::swap32(m_header_2.compression_type);
if (compression_type != 0)
m_compression_type = static_cast<CompressionType>(compression_type);
if (m_compression_type > CompressionType::Purge)
{
ERROR_LOG(DISCIO, "Unsupported WIA compression type %u in %s", compression_type, path.c_str());
return false;
@ -114,26 +117,30 @@ bool WIAFileReader::Initialize(const std::string& path)
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))
if (!ReadCompressedData(number_of_raw_data_entries * sizeof(RawDataEntry),
Common::swap64(m_header_2.raw_data_entries_offset),
Common::swap32(m_header_2.raw_data_entries_size),
reinterpret_cast<u8*>(m_raw_data_entries.data()), false))
{
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))
if (!ReadCompressedData(number_of_group_entries * sizeof(GroupEntry),
Common::swap64(m_header_2.group_entries_offset),
Common::swap32(m_header_2.group_entries_size),
reinterpret_cast<u8*>(m_group_entries.data()), false))
{
return false;
}
return true;
}
@ -239,24 +246,13 @@ bool WIAFileReader::ReadFromGroups(u64* offset, u64* size, u8** out_ptr, u64 chu
const u64 group_offset = data_offset + i * chunk_size;
const u64 offset_in_group = *offset - group_offset;
// TODO: Compression
u64 group_offset_in_file = static_cast<u64>(Common::swap32(group.data_offset)) << 2;
if (exception_list)
{
u16 exceptions;
if (!m_file.Seek(group_offset_in_file, SEEK_SET) || !m_file.ReadArray(&exceptions, 1))
return false;
group_offset_in_file += Common::AlignUp(
sizeof(exceptions) + Common::swap16(exceptions) * sizeof(HashExceptionEntry), 4);
}
const u64 offset_in_file = group_offset_in_file + offset_in_group;
const u64 group_offset_in_file = static_cast<u64>(Common::swap32(group.data_offset)) << 2;
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))
if (!ReadCompressedData(chunk_size, group_offset_in_file, Common::swap32(group.data_size),
offset_in_group, bytes_to_read, *out_ptr, exception_list))
{
return false;
}
*offset += bytes_to_read;
*size -= bytes_to_read;
@ -266,6 +262,131 @@ bool WIAFileReader::ReadFromGroups(u64* offset, u64* size, u8** out_ptr, u64 chu
return true;
}
bool WIAFileReader::ReadCompressedData(u32 decompressed_data_size, u64 data_offset, u64 data_size,
u8* out_ptr, bool exception_list)
{
switch (m_compression_type)
{
case CompressionType::None:
{
return ReadCompressedData(decompressed_data_size, data_offset, data_size, 0,
decompressed_data_size, out_ptr, exception_list);
}
case CompressionType::Purge:
{
if (!m_file.Seek(data_offset, SEEK_SET))
return false;
if (exception_list)
{
const std::optional<u64> exception_size = ReadExceptionListFromFile();
if (!exception_size)
return false;
data_size -= *exception_size;
}
const u64 hash_offset = data_size - sizeof(SHA1);
u32 offset_in_data = 0;
u32 offset_in_decompressed_data = 0;
while (offset_in_data < hash_offset)
{
PurgeSegment purge_segment;
if (!m_file.ReadArray(&purge_segment, 1))
return false;
const u32 segment_offset = Common::swap32(purge_segment.offset);
const u32 segment_size = Common::swap32(purge_segment.size);
if (segment_offset < offset_in_decompressed_data)
return false;
const u32 blank_bytes = segment_offset - offset_in_decompressed_data;
std::memset(out_ptr, 0, blank_bytes);
out_ptr += blank_bytes;
if (segment_size != 0 && !m_file.ReadBytes(out_ptr, segment_size))
return false;
out_ptr += segment_size;
offset_in_data += sizeof(PurgeSegment) + segment_size;
offset_in_decompressed_data = segment_offset + segment_size;
}
if (offset_in_data != hash_offset || offset_in_decompressed_data > decompressed_data_size)
return false;
std::memset(out_ptr, 0, decompressed_data_size - offset_in_decompressed_data);
SHA1 expected_hash;
if (!m_file.ReadArray(&expected_hash, 1))
return false;
// TODO: Check hash
return true;
}
}
return false;
}
bool WIAFileReader::ReadCompressedData(u32 decompressed_data_size, u64 data_offset, u64 data_size,
u64 offset_in_data, u64 size_in_data, u8* out_ptr,
bool exception_list)
{
if (m_compression_type == CompressionType::None)
{
if (!m_file.Seek(data_offset, SEEK_SET))
return false;
if (exception_list)
{
const std::optional<u64> exception_list_size = ReadExceptionListFromFile();
if (!exception_list_size)
return false;
data_size -= *exception_list_size;
}
if (!m_file.Seek(offset_in_data, SEEK_CUR) || !m_file.ReadBytes(out_ptr, size_in_data))
return false;
return true;
}
else
{
// TODO: Caching
std::vector<u8> buffer(decompressed_data_size);
if (!ReadCompressedData(decompressed_data_size, data_offset, data_size, buffer.data(),
exception_list))
{
return false;
}
std::memcpy(out_ptr, buffer.data() + offset_in_data, size_in_data);
return true;
}
}
std::optional<u64> WIAFileReader::ReadExceptionListFromFile()
{
u16 exceptions;
if (!m_file.ReadArray(&exceptions, 1))
return std::nullopt;
const u64 exception_list_size = Common::AlignUp(
sizeof(exceptions) + Common::swap16(exceptions) * sizeof(HashExceptionEntry), 4);
if (!m_file.Seek(exception_list_size - sizeof(exceptions), SEEK_CUR))
return std::nullopt;
// TODO: Actually handle the exceptions
return exception_list_size;
}
std::string WIAFileReader::VersionToString(u32 version)
{
const u8 a = version >> 24;

View File

@ -6,6 +6,7 @@
#include <array>
#include <memory>
#include <utility>
#include "Common/CommonTypes.h"
#include "Common/File.h"
@ -43,6 +44,13 @@ private:
bool ReadFromGroups(u64* offset, u64* size, u8** out_ptr, u64 chunk_size, u32 sector_size,
u64 data_offset, u64 data_size, u32 group_index, u32 number_of_groups,
bool exception_list);
bool ReadCompressedData(u32 decompressed_data_size, u64 data_offset, u64 data_size, u8* out_ptr,
bool exception_list);
bool ReadCompressedData(u32 decompressed_data_size, u64 data_offset, u64 data_size,
u64 offset_in_data, u64 size_in_data, u8* out_ptr, bool exception_list);
// Returns the number of bytes read
std::optional<u64> ReadExceptionListFromFile();
static std::string VersionToString(u32 version);
@ -128,9 +136,26 @@ private:
SHA1 hash;
};
static_assert(sizeof(HashExceptionEntry) == 0x16, "Wrong size for WIA hash exception entry");
struct PurgeSegment
{
u32 offset;
u32 size;
};
static_assert(sizeof(PurgeSegment) == 0x08, "Wrong size for WIA purge segment");
#pragma pack(pop)
enum class CompressionType : u32
{
None = 0,
Purge = 1,
Bzip2 = 2,
LZMA = 3,
LZMA2 = 4,
};
bool m_valid;
CompressionType m_compression_type;
File::IOFile m_file;