Merge pull request #8538 from JosJuice/wia

Add support for the WIA and RVZ disc image formats
This commit is contained in:
Tilka
2020-06-21 11:40:58 +01:00
committed by GitHub
25 changed files with 4334 additions and 98 deletions
@@ -22,7 +22,7 @@ import java.util.List;
public final class FileBrowserHelper
{
public static final HashSet<String> GAME_EXTENSIONS = new HashSet<>(Arrays.asList(
"gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wad", "dol", "elf", "dff"));
"gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "wad", "dol", "elf", "dff"));
public static final HashSet<String> RAW_EXTENSION = new HashSet<>(Collections.singletonList(
"raw"));
+1 -1
View File
@@ -159,7 +159,7 @@ BootParameters::GenerateFromFile(std::vector<std::string> paths,
paths.clear();
static const std::unordered_set<std::string> disc_image_extensions = {
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".dol", ".elf"}};
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".dol", ".elf"}};
if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive)
{
std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDisc(path);
+5
View File
@@ -20,6 +20,7 @@
#include "DiscIO/DriveBlob.h"
#include "DiscIO/FileBlob.h"
#include "DiscIO/TGCBlob.h"
#include "DiscIO/WIABlob.h"
#include "DiscIO/WbfsBlob.h"
namespace DiscIO
@@ -205,6 +206,10 @@ std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename)
return TGCFileReader::Create(std::move(file));
case WBFS_MAGIC:
return WbfsFileReader::Create(std::move(file), filename);
case WIA_MAGIC:
return WIAFileReader::Create(std::move(file), filename);
case RVZ_MAGIC:
return RVZFileReader::Create(std::move(file), filename);
default:
if (auto directory_blob = DirectoryBlobReader::Create(filename))
return std::move(directory_blob);
+9 -1
View File
@@ -25,6 +25,8 @@
namespace DiscIO
{
enum class WIARVZCompressionType : u32;
// Increment CACHE_REVISION (GameFileCache.cpp) if the enum below is modified
enum class BlobType
{
@@ -34,7 +36,9 @@ enum class BlobType
GCZ,
CISO,
WBFS,
TGC
TGC,
WIA,
RVZ,
};
class BlobReader
@@ -172,5 +176,9 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
const std::string& outfile_path, CompressCB callback = nullptr,
void* arg = nullptr);
bool ConvertToWIAOrRVZ(BlobReader* infile, const std::string& infile_path,
const std::string& outfile_path, bool rvz,
WIARVZCompressionType compression_type, int compression_level,
int chunk_size, CompressCB callback = nullptr, void* arg = nullptr);
} // namespace DiscIO
+11
View File
@@ -21,6 +21,8 @@ add_library(discio
FileSystemGCWii.h
Filesystem.cpp
Filesystem.h
LaggedFibonacciGenerator.cpp
LaggedFibonacciGenerator.h
MultithreadedCompressor.h
NANDImporter.cpp
NANDImporter.h
@@ -42,6 +44,10 @@ add_library(discio
VolumeWii.h
WbfsBlob.cpp
WbfsBlob.h
WIABlob.cpp
WIABlob.h
WIACompression.cpp
WIACompression.h
WiiEncryptionCache.cpp
WiiEncryptionCache.h
WiiSaveBanner.cpp
@@ -49,6 +55,11 @@ add_library(discio
)
target_link_libraries(discio
PUBLIC
BZip2::BZip2
LibLZMA::LibLZMA
zstd
PRIVATE
minizip
pugixml
+15
View File
@@ -55,6 +55,7 @@
<ClCompile Include="FileBlob.cpp" />
<ClCompile Include="Filesystem.cpp" />
<ClCompile Include="FileSystemGCWii.cpp" />
<ClCompile Include="LaggedFibonacciGenerator.cpp" />
<ClCompile Include="NANDImporter.cpp" />
<ClCompile Include="ScrubbedBlob.cpp" />
<ClCompile Include="TGCBlob.cpp" />
@@ -65,6 +66,8 @@
<ClCompile Include="VolumeWad.cpp" />
<ClCompile Include="VolumeWii.cpp" />
<ClCompile Include="WbfsBlob.cpp" />
<ClCompile Include="WIABlob.cpp" />
<ClCompile Include="WIACompression.cpp" />
<ClCompile Include="WiiEncryptionCache.cpp" />
<ClCompile Include="WiiSaveBanner.cpp" />
</ItemGroup>
@@ -80,6 +83,7 @@
<ClInclude Include="FileBlob.h" />
<ClInclude Include="Filesystem.h" />
<ClInclude Include="FileSystemGCWii.h" />
<ClInclude Include="LaggedFibonacciGenerator.h" />
<ClInclude Include="MultithreadedCompressor.h" />
<ClInclude Include="NANDImporter.h" />
<ClInclude Include="ScrubbedBlob.h" />
@@ -91,6 +95,8 @@
<ClInclude Include="VolumeWad.h" />
<ClInclude Include="VolumeWii.h" />
<ClInclude Include="WbfsBlob.h" />
<ClInclude Include="WIABlob.h" />
<ClInclude Include="WIACompression.h" />
<ClInclude Include="WiiEncryptionCache.h" />
<ClInclude Include="WiiSaveBanner.h" />
</ItemGroup>
@@ -110,6 +116,15 @@
<ProjectReference Include="$(ExternalsDir)pugixml\pugixml.vcxproj">
<Project>{38fee76f-f347-484b-949c-b4649381cffb}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)bzip2\bzip2.vcxproj">
<Project>{055a775f-b4f5-4970-9240-f6cf7661f37b}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)liblzma\liblzma.vcxproj">
<Project>{1d8c51d2-ffa4-418e-b183-9f42b6a6717e}</Project>
</ProjectReference>
<ProjectReference Include="$(ExternalsDir)zstd\zstd.vcxproj">
<Project>{1bea10f3-80ce-4bc4-9331-5769372cdf99}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
+18
View File
@@ -90,6 +90,15 @@
<ClCompile Include="ScrubbedBlob.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
<ClCompile Include="WIABlob.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
<ClCompile Include="LaggedFibonacciGenerator.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
<ClCompile Include="WIACompression.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="DiscScrubber.h">
@@ -164,6 +173,15 @@
<ClInclude Include="MultithreadedCompressor.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
<ClInclude Include="WIABlob.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
<ClInclude Include="LaggedFibonacciGenerator.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
<ClInclude Include="WIACompression.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
@@ -0,0 +1,212 @@
// This file is under the public domain.
#include "DiscIO/LaggedFibonacciGenerator.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Swap.h"
namespace DiscIO
{
void LaggedFibonacciGenerator::SetSeed(const u32 seed[SEED_SIZE])
{
SetSeed(reinterpret_cast<const u8*>(seed));
}
void LaggedFibonacciGenerator::SetSeed(const u8 seed[SEED_SIZE * sizeof(u32)])
{
m_position_bytes = 0;
for (size_t i = 0; i < SEED_SIZE; ++i)
m_buffer[i] = Common::swap32(seed + i * sizeof(u32));
Initialize(false);
}
size_t LaggedFibonacciGenerator::GetSeed(const u8* data, size_t size, size_t data_offset,
u32 seed_out[SEED_SIZE])
{
if ((reinterpret_cast<uintptr_t>(data) - data_offset) % alignof(u32) != 0)
{
ASSERT(false);
return 0;
}
// For code simplicity, only include whole u32 words when regenerating the seed. It would be
// possible to get rid of this restriction and use a few additional bytes, but it's probably more
// effort than it's worth considering that junk data often starts or ends on 4-byte offsets.
const size_t bytes_to_skip = Common::AlignUp(data_offset, sizeof(u32)) - data_offset;
const u32* u32_data = reinterpret_cast<const u32*>(data + bytes_to_skip);
const size_t u32_size = (size - bytes_to_skip) / sizeof(u32);
const size_t u32_data_offset = (data_offset + bytes_to_skip) / sizeof(u32);
LaggedFibonacciGenerator lfg;
if (!GetSeed(u32_data, u32_size, u32_data_offset, &lfg, seed_out))
return false;
lfg.m_position_bytes = data_offset % (LFG_K * sizeof(u32));
const u8* end = data + size;
size_t reconstructed_bytes = 0;
while (data < end && lfg.GetByte() == *data)
{
++reconstructed_bytes;
++data;
}
return reconstructed_bytes;
}
bool LaggedFibonacciGenerator::GetSeed(const u32* data, size_t size, size_t data_offset,
LaggedFibonacciGenerator* lfg, u32 seed_out[SEED_SIZE])
{
if (size < LFG_K)
return false;
// If the data doesn't look like something we can regenerate, return early to save time
if (!std::all_of(data, data + LFG_K, [](u32 x) {
return (Common::swap32(x) & 0x00C00000) == (Common::swap32(x) >> 2 & 0x00C00000);
}))
{
return false;
}
const size_t data_offset_mod_k = data_offset % LFG_K;
const size_t data_offset_div_k = data_offset / LFG_K;
std::copy(data, data + LFG_K - data_offset_mod_k, lfg->m_buffer.data() + data_offset_mod_k);
std::copy(data + LFG_K - data_offset_mod_k, data + LFG_K, lfg->m_buffer.data());
lfg->Backward(0, data_offset_mod_k);
for (size_t i = 0; i < data_offset_div_k; ++i)
lfg->Backward();
if (!lfg->Reinitialize(seed_out))
return false;
for (size_t i = 0; i < data_offset_div_k; ++i)
lfg->Forward();
return true;
}
void LaggedFibonacciGenerator::GetBytes(size_t count, u8* out)
{
while (count > 0)
{
const size_t length = std::min(count, LFG_K * sizeof(u32) - m_position_bytes);
std::memcpy(out, reinterpret_cast<u8*>(m_buffer.data()) + m_position_bytes, length);
m_position_bytes += length;
count -= length;
out += length;
if (m_position_bytes == LFG_K * sizeof(u32))
{
Forward();
m_position_bytes = 0;
}
}
}
u8 LaggedFibonacciGenerator::GetByte()
{
const u8 result = reinterpret_cast<u8*>(m_buffer.data())[m_position_bytes];
++m_position_bytes;
if (m_position_bytes == LFG_K * sizeof(u32))
{
Forward();
m_position_bytes = 0;
}
return result;
}
void LaggedFibonacciGenerator::Forward(size_t count)
{
m_position_bytes += count;
while (m_position_bytes >= LFG_K * sizeof(u32))
{
Forward();
m_position_bytes -= LFG_K * sizeof(u32);
}
}
void LaggedFibonacciGenerator::Forward()
{
for (size_t i = 0; i < LFG_J; ++i)
m_buffer[i] ^= m_buffer[i + LFG_K - LFG_J];
for (size_t i = LFG_J; i < LFG_K; ++i)
m_buffer[i] ^= m_buffer[i - LFG_J];
}
void LaggedFibonacciGenerator::Backward(size_t start_word, size_t end_word)
{
const size_t loop_end = std::max(LFG_J, start_word);
for (size_t i = std::min(end_word, LFG_K); i > loop_end; --i)
m_buffer[i - 1] ^= m_buffer[i - 1 - LFG_J];
for (size_t i = std::min(end_word, LFG_J); i > start_word; --i)
m_buffer[i - 1] ^= m_buffer[i - 1 + LFG_K - LFG_J];
}
bool LaggedFibonacciGenerator::Reinitialize(u32 seed_out[SEED_SIZE])
{
for (size_t i = 0; i < 4; ++i)
Backward();
for (u32& x : m_buffer)
x = Common::swap32(x);
// Reconstruct the bits which are missing due to the output code shifting by 18 instead of 16.
// Unfortunately we can't reconstruct bits 16 and 17 (counting LSB as 0) for the first word,
// but the observable result (when shifting by 18 instead of 16) is not affected by this.
for (size_t i = 0; i < SEED_SIZE; ++i)
{
m_buffer[i] = (m_buffer[i] & 0xFF00FFFF) | (m_buffer[i] << 2 & 0x00FC0000) |
((m_buffer[i + 16] ^ m_buffer[i + 15]) << 9 & 0x00030000);
}
for (size_t i = 0; i < SEED_SIZE; ++i)
seed_out[i] = Common::swap32(m_buffer[i]);
return Initialize(true);
}
bool LaggedFibonacciGenerator::Initialize(bool check_existing_data)
{
for (size_t i = SEED_SIZE; i < LFG_K; ++i)
{
const u32 calculated = (m_buffer[i - 17] << 23) ^ (m_buffer[i - 16] >> 9) ^ m_buffer[i - 1];
if (check_existing_data)
{
const u32 actual = (m_buffer[i] & 0xFF00FFFF) | (m_buffer[i] << 2 & 0x00FC0000);
if ((calculated & 0xFFFCFFFF) != actual)
return false;
}
m_buffer[i] = calculated;
}
// Instead of doing the "shift by 18 instead of 16" oddity when actually outputting the data,
// we can do the shifting (and byteswapping) at this point to make the output code simpler.
for (u32& x : m_buffer)
x = Common::swap32((x & 0xFF00FFFF) | ((x >> 2) & 0x00FF0000));
for (size_t i = 0; i < 4; ++i)
Forward();
return true;
}
} // namespace DiscIO
@@ -0,0 +1,51 @@
// This file is under the public domain.
#pragma once
#include <array>
#include <cstddef>
#include "Common/CommonTypes.h"
namespace DiscIO
{
class LaggedFibonacciGenerator
{
public:
static constexpr size_t SEED_SIZE = 17;
// Reconstructs a seed and writes it to seed_out, then returns the number of bytes which can
// be reconstructed using that seed. Can return any number between 0 and size, inclusive.
// data - data_offset must be 4-byte aligned.
static size_t GetSeed(const u8* data, size_t size, size_t data_offset, u32 seed_out[SEED_SIZE]);
// SetSeed must be called before using the functions below
void SetSeed(const u32 seed[SEED_SIZE]);
void SetSeed(const u8 seed[SEED_SIZE * sizeof(u32)]);
// Outputs a number of bytes and advances the internal state by the same amount.
void GetBytes(size_t count, u8* out);
u8 GetByte();
// Advances the internal state like GetBytes, but without outputting data. O(N), like GetBytes.
void Forward(size_t count);
private:
static bool GetSeed(const u32* data, size_t size, size_t data_offset,
LaggedFibonacciGenerator* lfg, u32 seed_out[SEED_SIZE]);
void Forward();
void Backward(size_t start_word = 0, size_t end_word = LFG_K);
bool Reinitialize(u32 seed_out[SEED_SIZE]);
bool Initialize(bool check_existing_data);
static constexpr size_t LFG_K = 521;
static constexpr size_t LFG_J = 32;
std::array<u32, LFG_K> m_buffer;
size_t m_position_bytes = 0;
};
} // namespace DiscIO
+73 -65
View File
@@ -201,18 +201,9 @@ bool VolumeWii::Read(u64 offset, u64 length, u8* buffer, const Partition& partit
if (!m_reader->Read(block_offset_on_disc, BLOCK_TOTAL_SIZE, read_buffer.data()))
return false;
// Decrypt the block's data.
// 0x3D0 - 0x3DF in read_buffer will be overwritten,
// but that won't affect anything, because we won't
// use the content of read_buffer anymore after this
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_DATA_SIZE, &read_buffer[0x3D0],
&read_buffer[BLOCK_HEADER_SIZE], m_last_decrypted_block_data);
// Decrypt the block's data
DecryptBlockData(read_buffer.data(), m_last_decrypted_block_data, aes_context);
m_last_decrypted_block = block_offset_on_disc;
// The only thing we currently use from the 0x000 - 0x3FF part
// of the block is the IV (at 0x3D0), but it also contains SHA-1
// hashes that IOS uses to check that discs aren't tampered with.
// http://wiibrew.org/wiki/Wii_Disc#Encrypted
}
// Copy the decrypted data
@@ -482,14 +473,10 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const std::vector<u8>& encr
return false;
HashBlock hashes;
u8 iv[16] = {0};
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(HashBlock), iv,
encrypted_data.data(), reinterpret_cast<u8*>(&hashes));
DecryptBlockHashes(encrypted_data.data(), &hashes, aes_context);
u8 cluster_data[BLOCK_DATA_SIZE];
std::memcpy(iv, encrypted_data.data() + 0x3D0, 16);
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(cluster_data), iv,
encrypted_data.data() + sizeof(HashBlock), cluster_data);
DecryptBlockData(encrypted_data.data(), cluster_data, aes_context);
for (u32 hash_index = 0; hash_index < 31; ++hash_index)
{
@@ -532,54 +519,33 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const Partition& partition)
return CheckBlockIntegrity(block_index, cluster, partition);
}
bool VolumeWii::EncryptGroup(u64 offset, u64 partition_data_offset,
u64 partition_data_decrypted_size,
const std::array<u8, AES_KEY_SIZE>& key, BlobReader* blob,
std::array<u8, GROUP_TOTAL_SIZE>* out)
bool VolumeWii::HashGroup(const std::array<u8, BLOCK_DATA_SIZE> in[BLOCKS_PER_GROUP],
HashBlock out[BLOCKS_PER_GROUP],
const std::function<bool(size_t block)>& read_function)
{
std::vector<std::array<u8, BLOCK_DATA_SIZE>> unencrypted_data(BLOCKS_PER_GROUP);
std::vector<HashBlock> unencrypted_hashes(BLOCKS_PER_GROUP);
std::array<std::future<void>, BLOCKS_PER_GROUP> hash_futures;
bool error_occurred = false;
bool success = true;
for (size_t i = 0; i < BLOCKS_PER_GROUP; ++i)
{
if (!error_occurred)
{
if (offset + (i + 1) * BLOCK_DATA_SIZE <= partition_data_decrypted_size)
{
if (!blob->ReadWiiDecrypted(offset + i * BLOCK_DATA_SIZE, BLOCK_DATA_SIZE,
unencrypted_data[i].data(), partition_data_offset))
{
error_occurred = true;
}
}
else
{
unencrypted_data[i].fill(0);
}
}
if (read_function && success)
success = read_function(i);
hash_futures[i] = std::async(std::launch::async, [&unencrypted_data, &unencrypted_hashes,
&hash_futures, error_occurred, i]() {
hash_futures[i] = std::async(std::launch::async, [&in, &out, &hash_futures, success, i]() {
const size_t h1_base = Common::AlignDown(i, 8);
if (!error_occurred)
if (success)
{
// H0 hashes
for (size_t j = 0; j < 31; ++j)
{
mbedtls_sha1_ret(unencrypted_data[i].data() + j * 0x400, 0x400,
unencrypted_hashes[i].h0[j]);
}
mbedtls_sha1_ret(in[i].data() + j * 0x400, 0x400, out[i].h0[j]);
// H0 padding
std::memset(unencrypted_hashes[i].padding_0, 0, sizeof(HashBlock::padding_0));
std::memset(out[i].padding_0, 0, sizeof(HashBlock::padding_0));
// H1 hash
mbedtls_sha1_ret(reinterpret_cast<u8*>(unencrypted_hashes[i].h0), sizeof(HashBlock::h0),
unencrypted_hashes[h1_base].h1[i - h1_base]);
mbedtls_sha1_ret(reinterpret_cast<u8*>(out[i].h0), sizeof(HashBlock::h0),
out[h1_base].h1[i - h1_base]);
}
if (i % 8 == 7)
@@ -587,21 +553,18 @@ bool VolumeWii::EncryptGroup(u64 offset, u64 partition_data_offset,
for (size_t j = 0; j < 7; ++j)
hash_futures[h1_base + j].get();
if (!error_occurred)
if (success)
{
// H1 padding
std::memset(unencrypted_hashes[h1_base].padding_1, 0, sizeof(HashBlock::padding_1));
std::memset(out[h1_base].padding_1, 0, sizeof(HashBlock::padding_1));
// H1 copies
for (size_t j = 1; j < 8; ++j)
{
std::memcpy(unencrypted_hashes[h1_base + j].h1, unencrypted_hashes[h1_base].h1,
sizeof(HashBlock::h1));
}
std::memcpy(out[h1_base + j].h1, out[h1_base].h1, sizeof(HashBlock::h1));
// H2 hash
mbedtls_sha1_ret(reinterpret_cast<u8*>(unencrypted_hashes[i].h1), sizeof(HashBlock::h1),
unencrypted_hashes[0].h2[h1_base / 8]);
mbedtls_sha1_ret(reinterpret_cast<u8*>(out[i].h1), sizeof(HashBlock::h1),
out[0].h2[h1_base / 8]);
}
if (i == BLOCKS_PER_GROUP - 1)
@@ -609,17 +572,14 @@ bool VolumeWii::EncryptGroup(u64 offset, u64 partition_data_offset,
for (size_t j = 0; j < 7; ++j)
hash_futures[j * 8 + 7].get();
if (!error_occurred)
if (success)
{
// H2 padding
std::memset(unencrypted_hashes[0].padding_2, 0, sizeof(HashBlock::padding_2));
std::memset(out[0].padding_2, 0, sizeof(HashBlock::padding_2));
// H2 copies
for (size_t j = 1; j < BLOCKS_PER_GROUP; ++j)
{
std::memcpy(unencrypted_hashes[j].h2, unencrypted_hashes[0].h2,
sizeof(HashBlock::h2));
}
std::memcpy(out[j].h2, out[0].h2, sizeof(HashBlock::h2));
}
}
}
@@ -629,9 +589,41 @@ bool VolumeWii::EncryptGroup(u64 offset, u64 partition_data_offset,
// Wait for all the async tasks to finish
hash_futures.back().get();
if (error_occurred)
return success;
}
bool VolumeWii::EncryptGroup(
u64 offset, u64 partition_data_offset, u64 partition_data_decrypted_size,
const std::array<u8, AES_KEY_SIZE>& key, BlobReader* blob,
std::array<u8, GROUP_TOTAL_SIZE>* out,
const std::function<void(HashBlock hash_blocks[BLOCKS_PER_GROUP])>& hash_exception_callback)
{
std::vector<std::array<u8, BLOCK_DATA_SIZE>> unencrypted_data(BLOCKS_PER_GROUP);
std::vector<HashBlock> unencrypted_hashes(BLOCKS_PER_GROUP);
const bool success =
HashGroup(unencrypted_data.data(), unencrypted_hashes.data(), [&](size_t block) {
if (offset + (block + 1) * BLOCK_DATA_SIZE <= partition_data_decrypted_size)
{
if (!blob->ReadWiiDecrypted(offset + block * BLOCK_DATA_SIZE, BLOCK_DATA_SIZE,
unencrypted_data[block].data(), partition_data_offset))
{
return false;
}
}
else
{
unencrypted_data[block].fill(0);
}
return true;
});
if (!success)
return false;
if (hash_exception_callback)
hash_exception_callback(unencrypted_hashes.data());
const unsigned int threads =
std::min(BLOCKS_PER_GROUP, std::max<unsigned int>(1, std::thread::hardware_concurrency()));
@@ -667,4 +659,20 @@ bool VolumeWii::EncryptGroup(u64 offset, u64 partition_data_offset,
return true;
}
void VolumeWii::DecryptBlockHashes(const u8* in, HashBlock* out, mbedtls_aes_context* aes_context)
{
std::array<u8, 16> iv;
iv.fill(0);
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(HashBlock), iv.data(), in,
reinterpret_cast<u8*>(out));
}
void VolumeWii::DecryptBlockData(const u8* in, u8* out, mbedtls_aes_context* aes_context)
{
std::array<u8, 16> iv;
std::copy(&in[0x3d0], &in[0x3e0], iv.data());
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_DATA_SIZE, iv.data(),
&in[BLOCK_HEADER_SIZE], out);
}
} // namespace DiscIO
+15 -1
View File
@@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <functional>
#include <map>
#include <memory>
#include <optional>
@@ -97,9 +98,22 @@ public:
u64 GetRawSize() const override;
const BlobReader& GetBlobReader() const;
// The in parameter can either contain all the data to begin with,
// or read_function can write data into the in parameter when called.
// The latter lets reading run in parallel with hashing.
// This function returns false iff read_function returns false.
static bool HashGroup(const std::array<u8, BLOCK_DATA_SIZE> in[BLOCKS_PER_GROUP],
HashBlock out[BLOCKS_PER_GROUP],
const std::function<bool(size_t block)>& read_function = {});
static bool EncryptGroup(u64 offset, u64 partition_data_offset, u64 partition_data_decrypted_size,
const std::array<u8, AES_KEY_SIZE>& key, BlobReader* blob,
std::array<u8, GROUP_TOTAL_SIZE>* out);
std::array<u8, GROUP_TOTAL_SIZE>* out,
const std::function<void(HashBlock hash_blocks[BLOCKS_PER_GROUP])>&
hash_exception_callback = {});
static void DecryptBlockHashes(const u8* in, HashBlock* out, mbedtls_aes_context* aes_context);
static void DecryptBlockData(const u8* in, u8* out, mbedtls_aes_context* aes_context);
protected:
u32 GetOffsetShift() const override { return 2; }
File diff suppressed because it is too large Load Diff
+390
View File
@@ -0,0 +1,390 @@
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <limits>
#include <map>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
#include "Common/CommonTypes.h"
#include "Common/File.h"
#include "Common/Swap.h"
#include "DiscIO/Blob.h"
#include "DiscIO/MultithreadedCompressor.h"
#include "DiscIO/WIACompression.h"
#include "DiscIO/WiiEncryptionCache.h"
namespace DiscIO
{
class FileSystem;
class VolumeDisc;
enum class WIARVZCompressionType : u32
{
None = 0,
Purge = 1,
Bzip2 = 2,
LZMA = 3,
LZMA2 = 4,
Zstd = 5,
};
std::pair<int, int> GetAllowedCompressionLevels(WIARVZCompressionType compression_type);
constexpr u32 WIA_MAGIC = 0x01414957; // "WIA\x1" (byteswapped to little endian)
constexpr u32 RVZ_MAGIC = 0x015A5652; // "RVZ\x1" (byteswapped to little endian)
template <bool RVZ>
class WIARVZFileReader : public BlobReader
{
public:
~WIARVZFileReader();
static std::unique_ptr<WIARVZFileReader> Create(File::IOFile file, const std::string& path);
BlobType GetBlobType() const override;
u64 GetRawSize() const override { return Common::swap64(m_header_1.wia_file_size); }
u64 GetDataSize() const override { return Common::swap64(m_header_1.iso_file_size); }
bool IsDataSizeAccurate() const override { return true; }
u64 GetBlockSize() const override { return Common::swap32(m_header_2.chunk_size); }
bool HasFastRandomAccessInBlock() const override { return false; }
bool Read(u64 offset, u64 size, u8* out_ptr) override;
bool SupportsReadWiiDecrypted() const override;
bool ReadWiiDecrypted(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset) override;
static ConversionResultCode Convert(BlobReader* infile, const VolumeDisc* infile_volume,
File::IOFile* outfile, WIARVZCompressionType compression_type,
int compression_level, int chunk_size, CompressCB callback,
void* arg);
private:
using SHA1 = std::array<u8, 20>;
using WiiKey = std::array<u8, 16>;
// See docs/WIA.md for details about the format
#pragma pack(push, 1)
struct WIAHeader1
{
u32 magic;
u32 version;
u32 version_compatible;
u32 header_2_size;
SHA1 header_2_hash;
u64 iso_file_size;
u64 wia_file_size;
SHA1 header_1_hash;
};
static_assert(sizeof(WIAHeader1) == 0x48, "Wrong size for WIA header 1");
struct WIAHeader2
{
u32 disc_type;
u32 compression_type;
u32 compression_level; // Informative only
u32 chunk_size;
std::array<u8, 0x80> disc_header;
u32 number_of_partition_entries;
u32 partition_entry_size;
u64 partition_entries_offset;
SHA1 partition_entries_hash;
u32 number_of_raw_data_entries;
u64 raw_data_entries_offset;
u32 raw_data_entries_size;
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<PartitionDataEntry, 2> 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 WIAGroupEntry
{
u32 data_offset; // >> 2
u32 data_size;
};
static_assert(sizeof(WIAGroupEntry) == 0x08, "Wrong size for WIA group entry");
struct RVZGroupEntry
{
u32 data_offset; // >> 2
u32 data_size;
u32 rvz_packed_size;
};
static_assert(sizeof(RVZGroupEntry) == 0x0c, "Wrong size for RVZ group entry");
using GroupEntry = std::conditional_t<RVZ, RVZGroupEntry, WIAGroupEntry>;
struct HashExceptionEntry
{
u16 offset;
SHA1 hash;
};
static_assert(sizeof(HashExceptionEntry) == 0x16, "Wrong size for WIA hash exception entry");
#pragma pack(pop)
struct DataEntry
{
u32 index;
bool is_partition;
u8 partition_data_index;
DataEntry(size_t index_) : index(static_cast<u32>(index_)), is_partition(false) {}
DataEntry(size_t index_, size_t partition_data_index_)
: index(static_cast<u32>(index_)), is_partition(true),
partition_data_index(static_cast<u8>(partition_data_index_))
{
}
};
class Chunk
{
public:
Chunk();
Chunk(File::IOFile* file, u64 offset_in_file, u64 compressed_size, u64 decompressed_size,
u32 exception_lists, bool compressed_exception_lists, u32 rvz_packed_size,
u64 data_offset, std::unique_ptr<Decompressor> decompressor);
bool Read(u64 offset, u64 size, u8* out_ptr);
// This can only be called once at least one byte of data has been read
void GetHashExceptions(std::vector<HashExceptionEntry>* exception_list,
u64 exception_list_index, u16 additional_offset) const;
template <typename T>
bool ReadAll(std::vector<T>* vector)
{
return Read(0, vector->size() * sizeof(T), reinterpret_cast<u8*>(vector->data()));
}
private:
bool Decompress();
bool HandleExceptions(const u8* data, size_t bytes_allocated, size_t bytes_written,
size_t* bytes_used, bool align);
DecompressionBuffer m_in;
DecompressionBuffer m_out;
size_t m_in_bytes_read = 0;
std::unique_ptr<Decompressor> m_decompressor = nullptr;
File::IOFile* m_file = nullptr;
u64 m_offset_in_file = 0;
size_t m_out_bytes_allocated_for_exceptions = 0;
size_t m_out_bytes_used_for_exceptions = 0;
size_t m_in_bytes_used_for_exceptions = 0;
u32 m_exception_lists = 0;
bool m_compressed_exception_lists = false;
u32 m_rvz_packed_size = 0;
u64 m_data_offset = 0;
};
explicit WIARVZFileReader(File::IOFile file, const std::string& path);
bool Initialize(const std::string& path);
bool HasDataOverlap() const;
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,
u32 exception_lists);
Chunk& ReadCompressedData(u64 offset_in_file, u64 compressed_size, u64 decompressed_size,
WIARVZCompressionType compression_type, u32 exception_lists = 0,
u32 rvz_packed_size = 0, u64 data_offset = 0);
static bool ApplyHashExceptions(const std::vector<HashExceptionEntry>& exception_list,
VolumeWii::HashBlock hash_blocks[VolumeWii::BLOCKS_PER_GROUP]);
static std::string VersionToString(u32 version);
struct ReuseID
{
bool operator==(const ReuseID& other) const
{
return std::tie(partition_key, data_size, encrypted, value) ==
std::tie(other.partition_key, other.data_size, other.encrypted, other.value);
}
bool operator<(const ReuseID& other) const
{
return std::tie(partition_key, data_size, encrypted, value) <
std::tie(other.partition_key, other.data_size, other.encrypted, other.value);
}
bool operator>(const ReuseID& other) const
{
return std::tie(partition_key, data_size, encrypted, value) >
std::tie(other.partition_key, other.data_size, other.encrypted, other.value);
}
bool operator!=(const ReuseID& other) const { return !operator==(other); }
bool operator>=(const ReuseID& other) const { return !operator<(other); }
bool operator<=(const ReuseID& other) const { return !operator>(other); }
const WiiKey* partition_key;
u64 data_size;
bool encrypted;
u8 value;
};
struct CompressThreadState
{
using WiiBlockData = std::array<u8, VolumeWii::BLOCK_DATA_SIZE>;
std::unique_ptr<Compressor> compressor;
std::vector<WiiBlockData> decryption_buffer =
std::vector<WiiBlockData>(VolumeWii::BLOCKS_PER_GROUP);
std::vector<VolumeWii::HashBlock> hash_buffer =
std::vector<VolumeWii::HashBlock>(VolumeWii::BLOCKS_PER_GROUP);
};
struct CompressParameters
{
std::vector<u8> data;
const DataEntry* data_entry;
u64 data_offset;
u64 bytes_read;
size_t group_index;
};
struct WIAOutputParametersEntry
{
std::vector<u8> exception_lists;
std::vector<u8> main_data;
std::optional<ReuseID> reuse_id;
std::optional<GroupEntry> reused_group;
};
struct RVZOutputParametersEntry
{
std::vector<u8> exception_lists;
std::vector<u8> main_data;
std::optional<ReuseID> reuse_id;
std::optional<GroupEntry> reused_group;
size_t rvz_packed_size = 0;
bool compressed = false;
};
using OutputParametersEntry =
std::conditional_t<RVZ, RVZOutputParametersEntry, WIAOutputParametersEntry>;
struct OutputParameters
{
std::vector<OutputParametersEntry> entries;
u64 bytes_read;
size_t group_index;
};
static bool PadTo4(File::IOFile* file, u64* bytes_written);
static void AddRawDataEntry(u64 offset, u64 size, int chunk_size, u32* total_groups,
std::vector<RawDataEntry>* raw_data_entries,
std::vector<DataEntry>* data_entries);
static PartitionDataEntry
CreatePartitionDataEntry(u64 offset, u64 size, u32 index, int chunk_size, u32* total_groups,
const std::vector<PartitionEntry>& partition_entries,
std::vector<DataEntry>* data_entries);
static ConversionResultCode SetUpDataEntriesForWriting(
const VolumeDisc* volume, int chunk_size, u64 iso_size, u32* total_groups,
std::vector<PartitionEntry>* partition_entries, std::vector<RawDataEntry>* raw_data_entries,
std::vector<DataEntry>* data_entries, std::vector<const FileSystem*>* partition_file_systems);
static std::optional<std::vector<u8>> Compress(Compressor* compressor, const u8* data,
size_t size);
static bool WriteHeader(File::IOFile* file, const u8* data, size_t size, u64 upper_bound,
u64* bytes_written, u64* offset_out);
static void SetUpCompressor(std::unique_ptr<Compressor>* compressor,
WIARVZCompressionType compression_type, int compression_level,
WIAHeader2* header_2);
static bool TryReuse(std::map<ReuseID, GroupEntry>* reusable_groups,
std::mutex* reusable_groups_mutex, OutputParametersEntry* entry);
static ConversionResult<OutputParameters>
ProcessAndCompress(CompressThreadState* state, CompressParameters parameters,
const std::vector<PartitionEntry>& partition_entries,
const std::vector<DataEntry>& data_entries, const FileSystem* file_system,
std::map<ReuseID, GroupEntry>* reusable_groups,
std::mutex* reusable_groups_mutex, u64 chunks_per_wii_group,
u64 exception_lists_per_chunk, bool compressed_exception_lists,
bool compression);
static ConversionResultCode Output(std::vector<OutputParametersEntry>* entries,
File::IOFile* outfile,
std::map<ReuseID, GroupEntry>* reusable_groups,
std::mutex* reusable_groups_mutex, GroupEntry* group_entry,
u64* bytes_written);
static ConversionResultCode RunCallback(size_t groups_written, u64 bytes_read, u64 bytes_written,
u32 total_groups, u64 iso_size, CompressCB callback,
void* arg);
bool m_valid;
WIARVZCompressionType m_compression_type;
File::IOFile m_file;
Chunk m_cached_chunk;
u64 m_cached_chunk_offset = std::numeric_limits<u64>::max();
WiiEncryptionCache m_encryption_cache;
std::vector<HashExceptionEntry> m_exception_list;
bool m_write_to_exception_list = false;
u64 m_exception_list_last_group_index;
WIAHeader1 m_header_1;
WIAHeader2 m_header_2;
std::vector<PartitionEntry> m_partition_entries;
std::vector<RawDataEntry> m_raw_data_entries;
std::vector<GroupEntry> m_group_entries;
std::map<u64, DataEntry> m_data_entries;
// Perhaps we could set WIA_VERSION_WRITE_COMPATIBLE to 0.9, but WIA version 0.9 was never in
// any official release of wit, and interim versions (either source or binaries) are hard to find.
// Since we've been unable to check if we're write compatible with 0.9, we set it 1.0 to be safe.
static constexpr u32 WIA_VERSION = 0x01000000;
static constexpr u32 WIA_VERSION_WRITE_COMPATIBLE = 0x01000000;
static constexpr u32 WIA_VERSION_READ_COMPATIBLE = 0x00080000;
static constexpr u32 RVZ_VERSION = 0x01000000;
static constexpr u32 RVZ_VERSION_WRITE_COMPATIBLE = 0x00030000;
static constexpr u32 RVZ_VERSION_READ_COMPATIBLE = 0x00030000;
};
using WIAFileReader = WIARVZFileReader<false>;
using RVZFileReader = WIARVZFileReader<true>;
} // namespace DiscIO
File diff suppressed because it is too large Load Diff
+252
View File
@@ -0,0 +1,252 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <memory>
#include <optional>
#include <vector>
#include <bzlib.h>
#include <lzma.h>
#include <mbedtls/sha1.h>
#include <zstd.h>
#include "Common/CommonTypes.h"
#include "DiscIO/LaggedFibonacciGenerator.h"
namespace DiscIO
{
struct DecompressionBuffer
{
std::vector<u8> data;
size_t bytes_written = 0;
};
using SHA1 = std::array<u8, 20>;
struct PurgeSegment
{
u32 offset;
u32 size;
};
static_assert(sizeof(PurgeSegment) == 0x08, "Wrong size for WIA purge segment");
class Decompressor
{
public:
virtual ~Decompressor();
virtual bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) = 0;
virtual bool Done() const { return m_done; };
protected:
bool m_done = false;
};
class NoneDecompressor final : public Decompressor
{
public:
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
};
// This class assumes that more bytes won't be added to in once in.bytes_written == in.data.size()
// and that *in_bytes_read initially will be equal to the size of the exception lists
class PurgeDecompressor final : public Decompressor
{
public:
PurgeDecompressor(u64 decompressed_size);
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
private:
const u64 m_decompressed_size;
PurgeSegment m_segment = {};
size_t m_bytes_read = 0;
size_t m_segment_bytes_written = 0;
size_t m_out_bytes_written = 0;
bool m_started = false;
mbedtls_sha1_context m_sha1_context;
};
class Bzip2Decompressor final : public Decompressor
{
public:
~Bzip2Decompressor();
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
private:
bz_stream m_stream = {};
bool m_started = false;
};
class LZMADecompressor final : public Decompressor
{
public:
LZMADecompressor(bool lzma2, const u8* filter_options, size_t filter_options_size);
~LZMADecompressor();
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
private:
lzma_stream m_stream = LZMA_STREAM_INIT;
lzma_options_lzma m_options = {};
lzma_filter m_filters[2];
bool m_started = false;
bool m_error_occurred = false;
};
class ZstdDecompressor final : public Decompressor
{
public:
ZstdDecompressor();
~ZstdDecompressor();
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
private:
ZSTD_DStream* m_stream;
};
class RVZPackDecompressor final : public Decompressor
{
public:
RVZPackDecompressor(std::unique_ptr<Decompressor> decompressor, DecompressionBuffer decompressed,
u64 data_offset, u32 rvz_packed_size);
bool Decompress(const DecompressionBuffer& in, DecompressionBuffer* out,
size_t* in_bytes_read) override;
bool Done() const override;
private:
bool IncrementBytesRead(size_t x);
std::optional<bool> ReadToDecompressed(const DecompressionBuffer& in, size_t* in_bytes_read,
size_t decompressed_bytes_read, size_t bytes_to_read);
std::unique_ptr<Decompressor> m_decompressor;
DecompressionBuffer m_decompressed;
size_t m_decompressed_bytes_read = 0;
size_t m_bytes_read;
u64 m_data_offset;
u32 m_rvz_packed_size;
u32 m_size = 0;
bool m_junk;
LaggedFibonacciGenerator m_lfg;
};
class Compressor
{
public:
virtual ~Compressor();
// First call Start, then AddDataOnlyForPurgeHashing/Compress any number of times,
// then End, then GetData/GetSize any number of times.
virtual bool Start() = 0;
virtual bool AddPrecedingDataOnlyForPurgeHashing(const u8* data, size_t size) { return true; }
virtual bool Compress(const u8* data, size_t size) = 0;
virtual bool End() = 0;
virtual const u8* GetData() const = 0;
virtual size_t GetSize() const = 0;
};
class PurgeCompressor final : public Compressor
{
public:
PurgeCompressor();
~PurgeCompressor();
bool Start() override;
bool AddPrecedingDataOnlyForPurgeHashing(const u8* data, size_t size) override;
bool Compress(const u8* data, size_t size) override;
bool End() override;
const u8* GetData() const override;
size_t GetSize() const override;
private:
std::vector<u8> m_buffer;
size_t m_bytes_written;
mbedtls_sha1_context m_sha1_context;
};
class Bzip2Compressor final : public Compressor
{
public:
Bzip2Compressor(int compression_level);
~Bzip2Compressor();
bool Start() override;
bool Compress(const u8* data, size_t size) override;
bool End() override;
const u8* GetData() const override;
size_t GetSize() const override;
private:
void ExpandBuffer(size_t bytes_to_add);
bz_stream m_stream = {};
std::vector<u8> m_buffer;
int m_compression_level;
};
class LZMACompressor final : public Compressor
{
public:
LZMACompressor(bool lzma2, int compression_level, u8 compressor_data_out[7],
u8* compressor_data_size_out);
~LZMACompressor();
bool Start() override;
bool Compress(const u8* data, size_t size) override;
bool End() override;
const u8* GetData() const override;
size_t GetSize() const override;
private:
void ExpandBuffer(size_t bytes_to_add);
lzma_stream m_stream = LZMA_STREAM_INIT;
lzma_options_lzma m_options = {};
lzma_filter m_filters[2];
std::vector<u8> m_buffer;
bool m_initialization_failed = false;
};
class ZstdCompressor final : public Compressor
{
public:
ZstdCompressor(int compression_level);
~ZstdCompressor();
bool Start() override;
bool Compress(const u8* data, size_t size) override;
bool End() override;
const u8* GetData() const override { return m_buffer.data(); }
size_t GetSize() const override { return m_out_buffer.pos; }
private:
void ExpandBuffer(size_t bytes_to_add);
ZSTD_CStream* m_stream;
ZSTD_outBuffer m_out_buffer;
std::vector<u8> m_buffer;
};
} // namespace DiscIO
+18 -4
View File
@@ -24,7 +24,8 @@ WiiEncryptionCache::~WiiEncryptionCache() = default;
const std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>*
WiiEncryptionCache::EncryptGroup(u64 offset, u64 partition_data_offset,
u64 partition_data_decrypted_size, const Key& key)
u64 partition_data_decrypted_size, const Key& key,
const HashExceptionCallback& hash_exception_callback)
{
// Only allocate memory if this function actually ends up getting called
if (!m_cache)
@@ -40,8 +41,20 @@ WiiEncryptionCache::EncryptGroup(u64 offset, u64 partition_data_offset,
if (m_cached_offset != group_offset_on_disc)
{
std::function<void(VolumeWii::HashBlock * hash_blocks)> hash_exception_callback_2;
if (hash_exception_callback)
{
hash_exception_callback_2 =
[offset, &hash_exception_callback](
VolumeWii::HashBlock hash_blocks[VolumeWii::BLOCKS_PER_GROUP]) {
return hash_exception_callback(hash_blocks, offset);
};
}
if (!VolumeWii::EncryptGroup(group_offset_in_partition, partition_data_offset,
partition_data_decrypted_size, key, m_blob, m_cache.get()))
partition_data_decrypted_size, key, m_blob, m_cache.get(),
hash_exception_callback_2))
{
m_cached_offset = std::numeric_limits<u64>::max(); // Invalidate the cache
return nullptr;
@@ -54,13 +67,14 @@ WiiEncryptionCache::EncryptGroup(u64 offset, u64 partition_data_offset,
}
bool WiiEncryptionCache::EncryptGroups(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset,
u64 partition_data_decrypted_size, const Key& key)
u64 partition_data_decrypted_size, const Key& key,
const HashExceptionCallback& hash_exception_callback)
{
while (size > 0)
{
const std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>* group =
EncryptGroup(Common::AlignDown(offset, VolumeWii::GROUP_TOTAL_SIZE), partition_data_offset,
partition_data_decrypted_size, key);
partition_data_decrypted_size, key, hash_exception_callback);
if (!group)
return false;
+7 -5
View File
@@ -19,6 +19,8 @@ class WiiEncryptionCache
{
public:
using Key = std::array<u8, VolumeWii::AES_KEY_SIZE>;
using HashExceptionCallback = std::function<void(
VolumeWii::HashBlock hash_blocks[VolumeWii::BLOCKS_PER_GROUP], u64 offset)>;
// The blob pointer is kept around for the lifetime of this object.
explicit WiiEncryptionCache(BlobReader* blob);
@@ -28,15 +30,15 @@ public:
// If the returned pointer is nullptr, reading from the blob failed.
// If the returned pointer is not nullptr, it is guaranteed to be valid until
// the next call of this function or the destruction of this object.
const std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>* EncryptGroup(u64 offset,
u64 partition_data_offset,
u64 partition_data_decrypted_size,
const Key& key);
const std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>*
EncryptGroup(u64 offset, u64 partition_data_offset, u64 partition_data_decrypted_size,
const Key& key, const HashExceptionCallback& hash_exception_callback = {});
// Encrypts a variable number of groups, as determined by the offset and size parameters.
// Supports reading groups partially.
bool EncryptGroups(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset,
u64 partition_data_decrypted_size, const Key& key);
u64 partition_data_decrypted_size, const Key& key,
const HashExceptionCallback& hash_exception_callback = {});
private:
BlobReader* m_blob;
+156 -13
View File
@@ -26,6 +26,7 @@
#include "Common/Logging/Log.h"
#include "DiscIO/Blob.h"
#include "DiscIO/ScrubbedBlob.h"
#include "DiscIO/WIABlob.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/ParallelProgressDialog.h"
#include "UICommon/GameFile.h"
@@ -57,6 +58,8 @@ ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> fi
m_format = new QComboBox;
m_format->addItem(QStringLiteral("ISO"), static_cast<int>(DiscIO::BlobType::PLAIN));
m_format->addItem(QStringLiteral("GCZ"), static_cast<int>(DiscIO::BlobType::GCZ));
m_format->addItem(QStringLiteral("WIA"), static_cast<int>(DiscIO::BlobType::WIA));
m_format->addItem(QStringLiteral("RVZ"), static_cast<int>(DiscIO::BlobType::RVZ));
if (std::all_of(m_files.begin(), m_files.end(),
[](const auto& file) { return file->GetBlobType() == DiscIO::BlobType::PLAIN; }))
{
@@ -69,9 +72,17 @@ ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> fi
grid_layout->addWidget(new QLabel(tr("Block Size:")), 1, 0);
grid_layout->addWidget(m_block_size, 1, 1);
m_compression = new QComboBox;
grid_layout->addWidget(new QLabel(tr("Compression:")), 2, 0);
grid_layout->addWidget(m_compression, 2, 1);
m_compression_level = new QComboBox;
grid_layout->addWidget(new QLabel(tr("Compression Level:")), 3, 0);
grid_layout->addWidget(m_compression_level, 3, 1);
m_scrub = new QCheckBox;
grid_layout->addWidget(new QLabel(tr("Remove Junk Data (Irreversible):")), 2, 0);
grid_layout->addWidget(m_scrub, 2, 1);
grid_layout->addWidget(new QLabel(tr("Remove Junk Data (Irreversible):")), 4, 0);
grid_layout->addWidget(m_scrub, 4, 1);
m_scrub->setEnabled(
std::none_of(m_files.begin(), m_files.end(), std::mem_fn(&UICommon::GameFile::IsDatelDisc)));
@@ -83,12 +94,17 @@ ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> fi
QGroupBox* options_group = new QGroupBox(tr("Options"));
options_group->setLayout(options_layout);
QLabel* info_text =
new QLabel(tr("ISO: A simple and robust format which is supported by many programs. "
"It takes up more space than any other format.\n\n"
"GCZ: A basic compressed format which is compatible with most versions of "
"Dolphin and some other programs. It can't efficiently compress junk data "
"(unless removed) or encrypted Wii data."));
QLabel* info_text = new QLabel(
tr("ISO: A simple and robust format which is supported by many programs. It takes up more "
"space than any other format.\n\n"
"GCZ: A basic compressed format which is compatible with most versions of Dolphin and "
"some other programs. It can't efficiently compress junk data (unless removed) or "
"encrypted Wii data.\n\n"
"WIA: An advanced compressed format which is compatible with recent versions of Dolphin "
"and a few other programs. It can efficiently compress encrypted Wii data, but not junk "
"data (unless removed).\n\n"
"RVZ: An advanced compressed format which is compatible with recent versions of Dolphin. "
"It can efficiently compress both junk data and encrypted Wii data."));
info_text->setWordWrap(true);
QVBoxLayout* info_layout = new QVBoxLayout;
@@ -104,14 +120,34 @@ ConvertDialog::ConvertDialog(QList<std::shared_ptr<const UICommon::GameFile>> fi
connect(m_format, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConvertDialog::OnFormatChanged);
connect(m_compression, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConvertDialog::OnCompressionChanged);
connect(convert_button, &QPushButton::clicked, this, &ConvertDialog::Convert);
OnFormatChanged();
OnCompressionChanged();
}
void ConvertDialog::AddToBlockSizeComboBox(int size)
{
m_block_size->addItem(QString::fromStdString(UICommon::FormatSize(size, 0)), size);
// Select 128 KiB by default, or if it is not available, the size closest to it.
// This code assumes that sizes get added to the combo box in increasing order.
constexpr int DEFAULT_SIZE = 0x20000;
if (size <= DEFAULT_SIZE)
m_block_size->setCurrentIndex(m_block_size->count() - 1);
}
void ConvertDialog::AddToCompressionComboBox(const QString& name,
DiscIO::WIARVZCompressionType type)
{
m_compression->addItem(name, static_cast<int>(type));
}
void ConvertDialog::AddToCompressionLevelComboBox(int level)
{
m_compression_level->addItem(QString::number(level), level);
}
void ConvertDialog::OnFormatChanged()
@@ -127,6 +163,9 @@ void ConvertDialog::OnFormatChanged()
const DiscIO::BlobType format = static_cast<DiscIO::BlobType>(m_format->currentData().toInt());
m_block_size->clear();
m_compression->clear();
// Populate m_block_size
switch (format)
{
case DiscIO::BlobType::GCZ:
@@ -166,11 +205,90 @@ void ConvertDialog::OnFormatChanged()
break;
}
case DiscIO::BlobType::WIA:
m_block_size->setEnabled(true);
// This is the smallest block size supported by WIA. For performance, larger sizes are avoided.
AddToBlockSizeComboBox(0x200000);
break;
case DiscIO::BlobType::RVZ:
m_block_size->setEnabled(true);
for (int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
AddToBlockSizeComboBox(block_size);
break;
default:
break;
}
// Populate m_compression
switch (format)
{
case DiscIO::BlobType::GCZ:
m_compression->setEnabled(true);
AddToCompressionComboBox(QStringLiteral("Deflate"), DiscIO::WIARVZCompressionType::None);
break;
case DiscIO::BlobType::WIA:
case DiscIO::BlobType::RVZ:
{
m_compression->setEnabled(true);
// i18n: %1 is the name of a compression method (e.g. LZMA)
const QString slow = tr("%1 (slow)");
AddToCompressionComboBox(tr("No Compression"), DiscIO::WIARVZCompressionType::None);
if (format == DiscIO::BlobType::WIA)
AddToCompressionComboBox(QStringLiteral("Purge"), DiscIO::WIARVZCompressionType::Purge);
AddToCompressionComboBox(slow.arg(QStringLiteral("bzip2")),
DiscIO::WIARVZCompressionType::Bzip2);
AddToCompressionComboBox(slow.arg(QStringLiteral("LZMA")), DiscIO::WIARVZCompressionType::LZMA);
AddToCompressionComboBox(slow.arg(QStringLiteral("LZMA2")),
DiscIO::WIARVZCompressionType::LZMA2);
if (format == DiscIO::BlobType::RVZ)
{
AddToCompressionComboBox(QStringLiteral("Zstandard"), DiscIO::WIARVZCompressionType::Zstd);
m_compression->setCurrentIndex(m_compression->count() - 1);
}
break;
}
default:
m_compression->setEnabled(false);
break;
}
m_block_size->setEnabled(m_block_size->count() > 1);
m_compression->setEnabled(m_compression->count() > 1);
m_scrub->setEnabled(format != DiscIO::BlobType::RVZ);
if (format == DiscIO::BlobType::RVZ)
m_scrub->setChecked(false);
}
void ConvertDialog::OnCompressionChanged()
{
m_compression_level->clear();
const auto compression_type =
static_cast<DiscIO::WIARVZCompressionType>(m_compression->currentData().toInt());
const std::pair<int, int> range = DiscIO::GetAllowedCompressionLevels(compression_type);
for (int i = range.first; i <= range.second; ++i)
{
AddToCompressionLevelComboBox(i);
if (i == 5)
m_compression_level->setCurrentIndex(m_compression_level->count() - 1);
}
m_compression_level->setEnabled(m_compression_level->count() > 1);
}
bool ConvertDialog::ShowAreYouSureDialog(const QString& text)
@@ -189,6 +307,9 @@ void ConvertDialog::Convert()
{
const DiscIO::BlobType format = static_cast<DiscIO::BlobType>(m_format->currentData().toInt());
const int block_size = m_block_size->currentData().toInt();
const DiscIO::WIARVZCompressionType compression =
static_cast<DiscIO::WIARVZCompressionType>(m_compression->currentData().toInt());
const int compression_level = m_compression_level->currentData().toInt();
const bool scrub = m_scrub->isChecked();
if (scrub && format == DiscIO::BlobType::PLAIN)
@@ -224,7 +345,15 @@ void ConvertDialog::Convert()
break;
case DiscIO::BlobType::GCZ:
extension = QStringLiteral(".gcz");
filter = tr("Compressed GC/Wii images (*.gcz)");
filter = tr("GCZ GC/Wii images (*.gcz)");
break;
case DiscIO::BlobType::WIA:
extension = QStringLiteral(".wia");
filter = tr("WIA GC/Wii images (*.wia)");
break;
case DiscIO::BlobType::RVZ:
extension = QStringLiteral(".rvz");
filter = tr("RVZ GC/Wii images (*.rvz)");
break;
default:
ASSERT(false);
@@ -330,8 +459,9 @@ void ConvertDialog::Convert()
{
std::future<bool> good;
if (format == DiscIO::BlobType::PLAIN)
switch (format)
{
case DiscIO::BlobType::PLAIN:
good = std::async(std::launch::async, [&] {
const bool good =
DiscIO::ConvertToPlain(blob_reader.get(), original_path, dst_path.toStdString(),
@@ -339,9 +469,9 @@ void ConvertDialog::Convert()
progress_dialog.Reset();
return good;
});
}
else if (format == DiscIO::BlobType::GCZ)
{
break;
case DiscIO::BlobType::GCZ:
good = std::async(std::launch::async, [&] {
const bool good =
DiscIO::ConvertToGCZ(blob_reader.get(), original_path, dst_path.toStdString(),
@@ -350,6 +480,19 @@ void ConvertDialog::Convert()
progress_dialog.Reset();
return good;
});
break;
case DiscIO::BlobType::WIA:
case DiscIO::BlobType::RVZ:
good = std::async(std::launch::async, [&] {
const bool good = DiscIO::ConvertToWIAOrRVZ(
blob_reader.get(), original_path, dst_path.toStdString(),
format == DiscIO::BlobType::RVZ, compression, compression_level, block_size,
&CompressCB, &progress_dialog);
progress_dialog.Reset();
return good;
});
break;
}
progress_dialog.GetRaw()->exec();
+10
View File
@@ -14,6 +14,11 @@
class QCheckBox;
class QComboBox;
namespace DiscIO
{
enum class WIARVZCompressionType : u32;
}
namespace UICommon
{
class GameFile;
@@ -29,15 +34,20 @@ public:
private slots:
void OnFormatChanged();
void OnCompressionChanged();
void Convert();
private:
void AddToBlockSizeComboBox(int size);
void AddToCompressionComboBox(const QString& name, DiscIO::WIARVZCompressionType type);
void AddToCompressionLevelComboBox(int level);
bool ShowAreYouSureDialog(const QString& text);
QComboBox* m_format;
QComboBox* m_block_size;
QComboBox* m_compression;
QComboBox* m_compression_level;
QCheckBox* m_scrub;
QList<std::shared_ptr<const UICommon::GameFile>> m_files;
};
@@ -24,6 +24,7 @@ static const QStringList game_filters{
QStringLiteral("*.[gG][cC][mM]"), QStringLiteral("*.[iI][sS][oO]"),
QStringLiteral("*.[tT][gG][cC]"), QStringLiteral("*.[cC][iI][sS][oO]"),
QStringLiteral("*.[gG][cC][zZ]"), QStringLiteral("*.[wW][bB][fF][sS]"),
QStringLiteral("*.[wW][iI][aA]"), QStringLiteral("*.[rR][vV][zZ]"),
QStringLiteral("*.[wW][aA][dD]"), QStringLiteral("*.[eE][lL][fF]"),
QStringLiteral("*.[dD][oO][lL]")};

Some files were not shown because too many files have changed in this diff Show More