diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 3c4ea03307..52b083ab63 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -58,7 +58,7 @@ public: } virtual bool SupportsReadWiiDecrypted() const { return false; } - virtual bool ReadWiiDecrypted(u64 offset, u64 size, u8* out_ptr, u64 partition_offset) + virtual bool ReadWiiDecrypted(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset) { return false; } diff --git a/Source/Core/DiscIO/CMakeLists.txt b/Source/Core/DiscIO/CMakeLists.txt index bfeab923fb..279e72030d 100644 --- a/Source/Core/DiscIO/CMakeLists.txt +++ b/Source/Core/DiscIO/CMakeLists.txt @@ -39,6 +39,8 @@ add_library(discio VolumeWii.h WbfsBlob.cpp WbfsBlob.h + WiiEncryptionCache.cpp + WiiEncryptionCache.h WiiSaveBanner.cpp WiiSaveBanner.h ) diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp index 3df7b44256..77ca32d19b 100644 --- a/Source/Core/DiscIO/DirectoryBlob.cpp +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -27,8 +27,10 @@ #include "Common/StringUtil.h" #include "Common/Swap.h" #include "Core/Boot/DolReader.h" +#include "Core/IOS/ES/Formats.h" #include "DiscIO/Blob.h" #include "DiscIO/VolumeWii.h" +#include "DiscIO/WiiEncryptionCache.h" namespace DiscIO { @@ -70,6 +72,11 @@ DiscContent::DiscContent(u64 offset, u64 size, const u8* data) { } +DiscContent::DiscContent(u64 offset, u64 size, DirectoryBlobReader* blob) + : m_offset(offset), m_size(size), m_content_source(blob) +{ +} + DiscContent::DiscContent(u64 offset) : m_offset(offset) { } @@ -107,11 +114,21 @@ bool DiscContent::Read(u64* offset, u64* length, u8** buffer) const if (!file.Seek(offset_in_content, SEEK_SET) || !file.ReadBytes(*buffer, bytes_to_read)) return false; } - else + else if (std::holds_alternative(m_content_source)) { const u8* const content_pointer = std::get(m_content_source) + offset_in_content; std::copy(content_pointer, content_pointer + bytes_to_read, *buffer); } + else + { + DirectoryBlobReader* blob = std::get(m_content_source); + const u64 decrypted_size = m_size * VolumeWii::BLOCK_DATA_SIZE / VolumeWii::BLOCK_TOTAL_SIZE; + if (!blob->EncryptPartitionData(offset_in_content, bytes_to_read, *buffer, m_offset, + decrypted_size)) + { + return false; + } + } *length -= bytes_to_read; *buffer += bytes_to_read; @@ -133,6 +150,12 @@ void DiscContentContainer::Add(u64 offset, u64 size, const u8* data) m_contents.emplace(offset, size, data); } +void DiscContentContainer::Add(u64 offset, u64 size, DirectoryBlobReader* blob) +{ + if (size != 0) + m_contents.emplace(offset, size, blob); +} + u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, const std::string& path) { const u64 size = File::GetSize(path); @@ -332,6 +355,7 @@ std::unique_ptr DirectoryBlobReader::Create(const std::stri DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, const std::string& true_root) + : m_encryption_cache(this) { DirectoryBlobPartition game_partition(game_partition_root, {}); m_is_wii = game_partition.IsWii(); @@ -340,6 +364,7 @@ DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, { m_gamecube_pseudopartition = std::move(game_partition); m_data_size = m_gamecube_pseudopartition.GetDataSize(); + m_encrypted = false; } else { @@ -377,7 +402,6 @@ bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer) if (offset + length > m_data_size) return false; - // TODO: We don't handle raw access to the encrypted area of Wii discs correctly. return (m_is_wii ? m_nonpartition_contents : m_gamecube_pseudopartition.GetContents()) .Read(offset, length, buffer); } @@ -387,12 +411,13 @@ bool DirectoryBlobReader::SupportsReadWiiDecrypted() const return m_is_wii; } -bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 partition_offset) +bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, + u64 partition_data_offset) { if (!m_is_wii) return false; - auto it = m_partitions.find(partition_offset); + auto it = m_partitions.find(partition_data_offset); if (it == m_partitions.end()) return false; @@ -402,6 +427,21 @@ bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 return it->second.GetContents().Read(offset, size, buffer); } +bool DirectoryBlobReader::EncryptPartitionData(u64 offset, u64 size, u8* buffer, + u64 partition_data_offset, + u64 partition_data_decrypted_size) +{ + auto it = m_partitions.find(partition_data_offset); + if (it == m_partitions.end()) + return false; + + if (!m_encrypted) + return it->second.GetContents().Read(offset, size, buffer); + + return m_encryption_cache.EncryptGroups(offset, size, buffer, partition_data_offset, + partition_data_decrypted_size, it->second.GetKey()); +} + BlobType DirectoryBlobReader::GetBlobType() const { return BlobType::DIRECTORY; @@ -439,6 +479,9 @@ void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector& parti if (header_bin_bytes_read < 0x61) m_disc_header_nonpartition[0x61] = 0; + m_encrypted = std::all_of(m_disc_header_nonpartition.data() + 0x60, + m_disc_header_nonpartition.data() + 0x64, [](u8 x) { return x == 0; }); + m_nonpartition_contents.Add(NONPARTITION_DISCHEADER_ADDRESS, m_disc_header_nonpartition); } @@ -511,12 +554,14 @@ void DirectoryBlobReader::SetPartitions(std::vector&& partiti Write32(static_cast(partitions[i].type), offset_in_table, &m_partition_table); offset_in_table += 4; - SetPartitionHeader(partitions[i].partition, partition_address); + SetPartitionHeader(&partitions[i].partition, partition_address); - const u64 partition_data_size = partitions[i].partition.GetDataSize(); - m_partitions.emplace(partition_address, std::move(partitions[i].partition)); + const u64 data_size = partitions[i].partition.GetDataSize(); + m_partitions.emplace(partition_address + PARTITION_DATA_OFFSET, + std::move(partitions[i].partition)); + m_nonpartition_contents.Add(partition_address + PARTITION_DATA_OFFSET, data_size, this); const u64 unaligned_next_partition_address = VolumeWii::EncryptedPartitionOffsetToRawOffset( - partition_data_size, Partition(partition_address), PARTITION_DATA_OFFSET); + data_size, Partition(partition_address), PARTITION_DATA_OFFSET); partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull); } m_data_size = partition_address; @@ -526,7 +571,7 @@ void DirectoryBlobReader::SetPartitions(std::vector&& partiti // This function sets the header that's shortly before the start of the encrypted // area, not the header that's right at the beginning of the encrypted area -void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& partition, +void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, u64 partition_address) { constexpr u32 TICKET_OFFSET = 0x0; @@ -536,10 +581,10 @@ void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& parti constexpr u32 H3_OFFSET = 0x4000; constexpr u32 H3_SIZE = 0x18000; - const std::string& partition_root = partition.GetRootDirectory(); + const std::string& partition_root = partition->GetRootDirectory(); - m_nonpartition_contents.CheckSizeAndAdd(partition_address + TICKET_OFFSET, TICKET_SIZE, - partition_root + "ticket.bin"); + const u64 ticket_size = m_nonpartition_contents.CheckSizeAndAdd( + partition_address + TICKET_OFFSET, TICKET_SIZE, partition_root + "ticket.bin"); const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd( partition_address + TMD_OFFSET, MAX_TMD_SIZE, partition_root + "tmd.bin"); @@ -553,7 +598,7 @@ void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& parti partition_root + "h3.bin"); constexpr u32 PARTITION_HEADER_SIZE = 0x1c; - const u64 data_size = Common::AlignUp(partition.GetDataSize(), 0x7c00) / 0x7c00 * 0x8000; + const u64 data_size = Common::AlignUp(partition->GetDataSize(), 0x7c00) / 0x7c00 * 0x8000; m_partition_headers.emplace_back(PARTITION_HEADER_SIZE); std::vector& partition_header = m_partition_headers.back(); Write32(static_cast(tmd_size), 0x0, &partition_header); @@ -565,6 +610,13 @@ void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& parti Write32(static_cast(data_size >> 2), 0x18, &partition_header); m_nonpartition_contents.Add(partition_address + TICKET_SIZE, partition_header); + + std::vector ticket_buffer(ticket_size); + m_nonpartition_contents.Read(partition_address + TICKET_OFFSET, ticket_size, + ticket_buffer.data()); + IOS::ES::TicketReader ticket(std::move(ticket_buffer)); + if (ticket.IsValid()) + partition->SetKey(ticket.GetTitleKey()); } DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory, diff --git a/Source/Core/DiscIO/DirectoryBlob.h b/Source/Core/DiscIO/DirectoryBlob.h index 0b15c3f807..3a23f19a0c 100644 --- a/Source/Core/DiscIO/DirectoryBlob.h +++ b/Source/Core/DiscIO/DirectoryBlob.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -16,6 +17,7 @@ #include "Common/CommonTypes.h" #include "Common/FileUtil.h" #include "DiscIO/Blob.h" +#include "DiscIO/WiiEncryptionCache.h" namespace File { @@ -27,16 +29,23 @@ namespace DiscIO { enum class PartitionType : u32; +class DirectoryBlobReader; + // Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself bool ShouldHideFromGameList(const std::string& volume_path); class DiscContent { public: - using ContentSource = std::variant; + using ContentSource = + std::variant; DiscContent(u64 offset, u64 size, const std::string& path); DiscContent(u64 offset, u64 size, const u8* data); + DiscContent(u64 offset, u64 size, DirectoryBlobReader* blob); // Provided because it's convenient when searching for DiscContent in an std::set explicit DiscContent(u64 offset); @@ -69,6 +78,7 @@ public: } void Add(u64 offset, u64 size, const std::string& path); void Add(u64 offset, u64 size, const u8* data); + void Add(u64 offset, u64 size, DirectoryBlobReader* blob); u64 CheckSizeAndAdd(u64 offset, const std::string& path); u64 CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path); @@ -96,6 +106,9 @@ public: const std::vector& GetHeader() const { return m_disc_header; } const DiscContentContainer& GetContents() const { return m_contents; } + const std::array& GetKey() const { return m_key; } + void SetKey(std::array key) { m_key = key; } + private: void SetDiscHeaderAndDiscType(std::optional is_wii); void SetBI2(); @@ -120,6 +133,8 @@ private: std::vector m_apploader; std::vector m_fst_data; + std::array m_key; + std::string m_root_directory; bool m_is_wii = false; // GameCube has no shift, Wii has 2 bit shift @@ -130,6 +145,8 @@ private: class DirectoryBlobReader : public BlobReader { + friend DiscContent; + public: static std::unique_ptr Create(const std::string& dol_path); @@ -141,7 +158,7 @@ public: bool Read(u64 offset, u64 length, u8* buffer) override; bool SupportsReadWiiDecrypted() const override; - bool ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 partition_offset) override; + bool ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 partition_data_offset) override; BlobType GetBlobType() const override; u64 GetRawSize() const override; @@ -163,11 +180,14 @@ private: explicit DirectoryBlobReader(const std::string& game_partition_root, const std::string& true_root); + bool EncryptPartitionData(u64 offset, u64 size, u8* buffer, u64 partition_data_offset, + u64 partition_data_decrypted_size); + void SetNonpartitionDiscHeader(const std::vector& partition_header, const std::string& game_partition_root); void SetWiiRegionData(const std::string& game_partition_root); void SetPartitions(std::vector&& partitions); - void SetPartitionHeader(const DirectoryBlobPartition& partition, u64 partition_address); + void SetPartitionHeader(DirectoryBlobPartition* partition, u64 partition_address); // For GameCube: DirectoryBlobPartition m_gamecube_pseudopartition; @@ -175,8 +195,10 @@ private: // For Wii: DiscContentContainer m_nonpartition_contents; std::map m_partitions; + WiiEncryptionCache m_encryption_cache; bool m_is_wii; + bool m_encrypted; std::vector m_disc_header_nonpartition; std::vector m_partition_table; diff --git a/Source/Core/DiscIO/DiscIO.vcxproj b/Source/Core/DiscIO/DiscIO.vcxproj index 7b0cf20bce..dd322dbb62 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj +++ b/Source/Core/DiscIO/DiscIO.vcxproj @@ -64,6 +64,7 @@ + @@ -87,6 +88,7 @@ + diff --git a/Source/Core/DiscIO/DiscIO.vcxproj.filters b/Source/Core/DiscIO/DiscIO.vcxproj.filters index 87fdbfadbc..5fb5891cfe 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj.filters +++ b/Source/Core/DiscIO/DiscIO.vcxproj.filters @@ -84,6 +84,9 @@ Volume + + Volume\Blob + @@ -149,6 +152,9 @@ Volume + + Volume\Blob + diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index d2a1fba3db..df6cfebee1 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -8,15 +8,19 @@ #include #include #include +#include #include -#include -#include #include #include #include +#include #include #include +#include +#include + +#include "Common/Align.h" #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -126,7 +130,7 @@ VolumeWii::VolumeWii(std::unique_ptr reader) const IOS::ES::TicketReader& ticket = *m_partitions[partition].ticket; if (!ticket.IsValid()) return nullptr; - const std::array key = ticket.GetTitleKey(); + const std::array key = ticket.GetTitleKey(); std::unique_ptr aes_context = std::make_unique(); mbedtls_aes_setkey_dec(aes_context.get(), key.data(), 128); return aes_context; @@ -162,14 +166,17 @@ bool VolumeWii::Read(u64 offset, u64 length, u8* buffer, const Partition& partit if (partition == PARTITION_NONE) return m_reader->Read(offset, length, buffer); - if (m_reader->SupportsReadWiiDecrypted()) - return m_reader->ReadWiiDecrypted(offset, length, buffer, partition.offset); - auto it = m_partitions.find(partition); if (it == m_partitions.end()) return false; const PartitionDetails& partition_details = it->second; + if (m_reader->SupportsReadWiiDecrypted()) + { + return m_reader->ReadWiiDecrypted(offset, length, buffer, + partition.offset + *partition_details.data_offset); + } + if (!m_encrypted) { return m_reader->Read(partition.offset + *partition_details.data_offset + offset, length, @@ -462,44 +469,43 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const std::vector& encr return false; const PartitionDetails& partition_details = it->second; - constexpr size_t SHA1_SIZE = 20; - if (block_index / 64 * SHA1_SIZE >= partition_details.h3_table->size()) + if (block_index / BLOCKS_PER_GROUP * SHA1_SIZE >= partition_details.h3_table->size()) return false; mbedtls_aes_context* aes_context = partition_details.key->get(); if (!aes_context) return false; - u8 cluster_metadata[BLOCK_HEADER_SIZE]; + HashBlock hashes; u8 iv[16] = {0}; - mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_HEADER_SIZE, iv, - encrypted_data.data(), cluster_metadata); + mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(HashBlock), iv, + encrypted_data.data(), reinterpret_cast(&hashes)); u8 cluster_data[BLOCK_DATA_SIZE]; std::memcpy(iv, encrypted_data.data() + 0x3D0, 16); - mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_DATA_SIZE, iv, - encrypted_data.data() + BLOCK_HEADER_SIZE, cluster_data); + mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(cluster_data), iv, + encrypted_data.data() + sizeof(HashBlock), cluster_data); for (u32 hash_index = 0; hash_index < 31; ++hash_index) { u8 h0_hash[SHA1_SIZE]; mbedtls_sha1_ret(cluster_data + hash_index * 0x400, 0x400, h0_hash); - if (memcmp(h0_hash, cluster_metadata + hash_index * SHA1_SIZE, SHA1_SIZE)) + if (memcmp(h0_hash, hashes.h0[hash_index], SHA1_SIZE)) return false; } u8 h1_hash[SHA1_SIZE]; - mbedtls_sha1_ret(cluster_metadata, SHA1_SIZE * 31, h1_hash); - if (memcmp(h1_hash, cluster_metadata + 0x280 + (block_index % 8) * SHA1_SIZE, SHA1_SIZE)) + mbedtls_sha1_ret(reinterpret_cast(hashes.h0), sizeof(hashes.h0), h1_hash); + if (memcmp(h1_hash, hashes.h1[block_index % 8], SHA1_SIZE)) return false; u8 h2_hash[SHA1_SIZE]; - mbedtls_sha1_ret(cluster_metadata + 0x280, SHA1_SIZE * 8, h2_hash); - if (memcmp(h2_hash, cluster_metadata + 0x340 + (block_index / 8 % 8) * SHA1_SIZE, SHA1_SIZE)) + mbedtls_sha1_ret(reinterpret_cast(hashes.h1), sizeof(hashes.h1), h2_hash); + if (memcmp(h2_hash, hashes.h2[block_index / 8 % 8], SHA1_SIZE)) return false; u8 h3_hash[SHA1_SIZE]; - mbedtls_sha1_ret(cluster_metadata + 0x340, SHA1_SIZE * 8, h3_hash); + mbedtls_sha1_ret(reinterpret_cast(hashes.h2), sizeof(hashes.h2), h3_hash); if (memcmp(h3_hash, partition_details.h3_table->data() + block_index / 64 * SHA1_SIZE, SHA1_SIZE)) return false; @@ -521,4 +527,139 @@ 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& key, BlobReader* blob, + std::array* out) +{ + std::vector> unencrypted_data(BLOCKS_PER_GROUP); + std::vector unencrypted_hashes(BLOCKS_PER_GROUP); + + std::array, BLOCKS_PER_GROUP> hash_futures; + bool error_occurred = false; + + 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); + } + } + + hash_futures[i] = std::async(std::launch::async, [&unencrypted_data, &unencrypted_hashes, + &hash_futures, error_occurred, i]() { + const size_t h1_base = Common::AlignDown(i, 8); + + if (!error_occurred) + { + // 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]); + } + + // H0 padding + std::memset(unencrypted_hashes[i].padding_0, 0, sizeof(HashBlock::padding_0)); + + // H1 hash + mbedtls_sha1_ret(reinterpret_cast(unencrypted_hashes[i].h0), sizeof(HashBlock::h0), + unencrypted_hashes[h1_base].h1[i - h1_base]); + } + + if (i % 8 == 7) + { + for (size_t j = 0; j < 7; ++j) + hash_futures[h1_base + j].get(); + + if (!error_occurred) + { + // H1 padding + std::memset(unencrypted_hashes[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)); + } + + // H2 hash + mbedtls_sha1_ret(reinterpret_cast(unencrypted_hashes[i].h1), sizeof(HashBlock::h1), + unencrypted_hashes[0].h2[h1_base / 8]); + } + + if (i == BLOCKS_PER_GROUP - 1) + { + for (size_t j = 0; j < 7; ++j) + hash_futures[j * 8 + 7].get(); + + if (!error_occurred) + { + // H2 padding + std::memset(unencrypted_hashes[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)); + } + } + } + } + }); + } + + // Wait for all the async tasks to finish + hash_futures.back().get(); + + if (error_occurred) + return false; + + const unsigned int threads = + std::min(BLOCKS_PER_GROUP, std::max(1, std::thread::hardware_concurrency())); + + std::vector> encryption_futures(threads); + + mbedtls_aes_context aes_context; + mbedtls_aes_setkey_enc(&aes_context, key.data(), 128); + + for (size_t i = 0; i < threads; ++i) + { + encryption_futures[i] = std::async( + std::launch::async, + [&unencrypted_data, &unencrypted_hashes, &aes_context, &out](size_t start, size_t end) { + for (size_t i = start; i < end; ++i) + { + u8* out_ptr = out->data() + i * BLOCK_TOTAL_SIZE; + + u8 iv[16] = {}; + mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_ENCRYPT, BLOCK_HEADER_SIZE, iv, + reinterpret_cast(&unencrypted_hashes[i]), out_ptr); + + std::memcpy(iv, out_ptr + 0x3D0, sizeof(iv)); + mbedtls_aes_crypt_cbc(&aes_context, MBEDTLS_AES_ENCRYPT, BLOCK_DATA_SIZE, iv, + unencrypted_data[i].data(), out_ptr + BLOCK_HEADER_SIZE); + } + }, + i * BLOCKS_PER_GROUP / threads, (i + 1) * BLOCKS_PER_GROUP / threads); + } + + for (std::future& future : encryption_futures) + future.get(); + + return true; +} + } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeWii.h b/Source/Core/DiscIO/VolumeWii.h index fb55169ad0..f82e919130 100644 --- a/Source/Core/DiscIO/VolumeWii.h +++ b/Source/Core/DiscIO/VolumeWii.h @@ -4,13 +4,15 @@ #pragma once +#include #include -#include #include #include #include #include +#include + #include "Common/CommonTypes.h" #include "Common/Lazy.h" #include "Core/IOS/ES/Formats.h" @@ -30,6 +32,31 @@ enum class Platform; class VolumeWii : public VolumeDisc { public: + static constexpr size_t AES_KEY_SIZE = 16; + static constexpr size_t SHA1_SIZE = 20; + + static constexpr u32 H3_TABLE_SIZE = 0x18000; + static constexpr u32 BLOCKS_PER_GROUP = 0x40; + + static constexpr u64 BLOCK_HEADER_SIZE = 0x0400; + static constexpr u64 BLOCK_DATA_SIZE = 0x7C00; + static constexpr u64 BLOCK_TOTAL_SIZE = BLOCK_HEADER_SIZE + BLOCK_DATA_SIZE; + + static constexpr u64 GROUP_HEADER_SIZE = BLOCK_HEADER_SIZE * BLOCKS_PER_GROUP; + static constexpr u64 GROUP_DATA_SIZE = BLOCK_DATA_SIZE * BLOCKS_PER_GROUP; + static constexpr u64 GROUP_TOTAL_SIZE = GROUP_HEADER_SIZE + GROUP_DATA_SIZE; + + struct HashBlock + { + u8 h0[31][SHA1_SIZE]; + u8 padding_0[20]; + u8 h1[8][SHA1_SIZE]; + u8 padding_1[32]; + u8 h2[8][SHA1_SIZE]; + u8 padding_2[32]; + }; + static_assert(sizeof(HashBlock) == BLOCK_HEADER_SIZE); + VolumeWii(std::unique_ptr reader); ~VolumeWii(); bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const override; @@ -69,11 +96,9 @@ public: bool IsSizeAccurate() const override; u64 GetRawSize() const override; - static constexpr unsigned int H3_TABLE_SIZE = 0x18000; - - static constexpr unsigned int BLOCK_HEADER_SIZE = 0x0400; - static constexpr unsigned int BLOCK_DATA_SIZE = 0x7C00; - static constexpr unsigned int BLOCK_TOTAL_SIZE = BLOCK_HEADER_SIZE + BLOCK_DATA_SIZE; + static bool EncryptGroup(u64 offset, u64 partition_data_offset, u64 partition_data_decrypted_size, + const std::array& key, BlobReader* blob, + std::array* out); protected: u32 GetOffsetShift() const override { return 2; } diff --git a/Source/Core/DiscIO/WiiEncryptionCache.cpp b/Source/Core/DiscIO/WiiEncryptionCache.cpp new file mode 100644 index 0000000000..c5a2111daa --- /dev/null +++ b/Source/Core/DiscIO/WiiEncryptionCache.cpp @@ -0,0 +1,80 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DiscIO/WiiEncryptionCache.h" + +#include +#include +#include +#include + +#include "Common/Align.h" +#include "Common/CommonTypes.h" +#include "DiscIO/Blob.h" +#include "DiscIO/VolumeWii.h" + +namespace DiscIO +{ +WiiEncryptionCache::WiiEncryptionCache(BlobReader* blob) : m_blob(blob) +{ +} + +WiiEncryptionCache::~WiiEncryptionCache() = default; + +const std::array* +WiiEncryptionCache::EncryptGroup(u64 offset, u64 partition_data_offset, + u64 partition_data_decrypted_size, const Key& key) +{ + // Only allocate memory if this function actually ends up getting called + if (!m_cache) + { + m_cache = std::make_unique>(); + ASSERT(m_blob->SupportsReadWiiDecrypted()); + } + + ASSERT(offset % VolumeWii::GROUP_TOTAL_SIZE == 0); + const u64 group_offset_in_partition = + offset / VolumeWii::GROUP_TOTAL_SIZE * VolumeWii::GROUP_DATA_SIZE; + const u64 group_offset_on_disc = partition_data_offset + offset; + + if (m_cached_offset != group_offset_on_disc) + { + if (!VolumeWii::EncryptGroup(group_offset_in_partition, partition_data_offset, + partition_data_decrypted_size, key, m_blob, m_cache.get())) + { + m_cached_offset = std::numeric_limits::max(); // Invalidate the cache + return nullptr; + } + + m_cached_offset = group_offset_on_disc; + } + + return m_cache.get(); +} + +bool WiiEncryptionCache::EncryptGroups(u64 offset, u64 size, u8* out_ptr, u64 partition_data_offset, + u64 partition_data_decrypted_size, const Key& key) +{ + while (size > 0) + { + const std::array* group = + EncryptGroup(Common::AlignDown(offset, VolumeWii::GROUP_TOTAL_SIZE), partition_data_offset, + partition_data_decrypted_size, key); + + if (!group) + return false; + + const u64 offset_in_group = offset % VolumeWii::GROUP_TOTAL_SIZE; + const u64 bytes_to_read = std::min(VolumeWii::GROUP_TOTAL_SIZE - offset_in_group, size); + std::memcpy(out_ptr, group->data() + offset_in_group, bytes_to_read); + + offset += bytes_to_read; + size -= bytes_to_read; + out_ptr += bytes_to_read; + } + + return true; +} + +} // namespace DiscIO diff --git a/Source/Core/DiscIO/WiiEncryptionCache.h b/Source/Core/DiscIO/WiiEncryptionCache.h new file mode 100644 index 0000000000..0c8f4b489a --- /dev/null +++ b/Source/Core/DiscIO/WiiEncryptionCache.h @@ -0,0 +1,47 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "DiscIO/VolumeWii.h" + +namespace DiscIO +{ +class BlobReader; + +class WiiEncryptionCache +{ +public: + using Key = std::array; + + // The blob pointer is kept around for the lifetime of this object. + explicit WiiEncryptionCache(BlobReader* blob); + ~WiiEncryptionCache(); + + // Encrypts exactly one group. + // 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* EncryptGroup(u64 offset, + u64 partition_data_offset, + u64 partition_data_decrypted_size, + const Key& key); + + // 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); + +private: + BlobReader* m_blob; + std::unique_ptr> m_cache; + u64 m_cached_offset = std::numeric_limits::max(); +}; + +} // namespace DiscIO