Merge pull request #8591 from JosJuice/wii-reencryption

DiscIO: Implement re-encryption of Wii partition data
This commit is contained in:
Markus Wick 2020-04-24 15:01:04 +02:00 committed by GitHub
commit 0a71dda8a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 419 additions and 42 deletions

View File

@ -58,7 +58,7 @@ public:
} }
virtual bool SupportsReadWiiDecrypted() const { return false; } 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; return false;
} }

View File

@ -39,6 +39,8 @@ add_library(discio
VolumeWii.h VolumeWii.h
WbfsBlob.cpp WbfsBlob.cpp
WbfsBlob.h WbfsBlob.h
WiiEncryptionCache.cpp
WiiEncryptionCache.h
WiiSaveBanner.cpp WiiSaveBanner.cpp
WiiSaveBanner.h WiiSaveBanner.h
) )

View File

@ -27,8 +27,10 @@
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Swap.h" #include "Common/Swap.h"
#include "Core/Boot/DolReader.h" #include "Core/Boot/DolReader.h"
#include "Core/IOS/ES/Formats.h"
#include "DiscIO/Blob.h" #include "DiscIO/Blob.h"
#include "DiscIO/VolumeWii.h" #include "DiscIO/VolumeWii.h"
#include "DiscIO/WiiEncryptionCache.h"
namespace DiscIO 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) 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)) if (!file.Seek(offset_in_content, SEEK_SET) || !file.ReadBytes(*buffer, bytes_to_read))
return false; return false;
} }
else else if (std::holds_alternative<const u8*>(m_content_source))
{ {
const u8* const content_pointer = std::get<const u8*>(m_content_source) + offset_in_content; const u8* const content_pointer = std::get<const u8*>(m_content_source) + offset_in_content;
std::copy(content_pointer, content_pointer + bytes_to_read, *buffer); std::copy(content_pointer, content_pointer + bytes_to_read, *buffer);
} }
else
{
DirectoryBlobReader* blob = std::get<DirectoryBlobReader*>(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; *length -= bytes_to_read;
*buffer += 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); 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) u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, const std::string& path)
{ {
const u64 size = File::GetSize(path); const u64 size = File::GetSize(path);
@ -332,6 +355,7 @@ std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(const std::stri
DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root,
const std::string& true_root) const std::string& true_root)
: m_encryption_cache(this)
{ {
DirectoryBlobPartition game_partition(game_partition_root, {}); DirectoryBlobPartition game_partition(game_partition_root, {});
m_is_wii = game_partition.IsWii(); 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_gamecube_pseudopartition = std::move(game_partition);
m_data_size = m_gamecube_pseudopartition.GetDataSize(); m_data_size = m_gamecube_pseudopartition.GetDataSize();
m_encrypted = false;
} }
else else
{ {
@ -377,7 +402,6 @@ bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer)
if (offset + length > m_data_size) if (offset + length > m_data_size)
return false; 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()) return (m_is_wii ? m_nonpartition_contents : m_gamecube_pseudopartition.GetContents())
.Read(offset, length, buffer); .Read(offset, length, buffer);
} }
@ -387,12 +411,13 @@ bool DirectoryBlobReader::SupportsReadWiiDecrypted() const
return m_is_wii; 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) if (!m_is_wii)
return false; return false;
auto it = m_partitions.find(partition_offset); auto it = m_partitions.find(partition_data_offset);
if (it == m_partitions.end()) if (it == m_partitions.end())
return false; return false;
@ -402,6 +427,21 @@ bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64
return it->second.GetContents().Read(offset, size, buffer); 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 BlobType DirectoryBlobReader::GetBlobType() const
{ {
return BlobType::DIRECTORY; return BlobType::DIRECTORY;
@ -439,6 +479,9 @@ void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector<u8>& parti
if (header_bin_bytes_read < 0x61) if (header_bin_bytes_read < 0x61)
m_disc_header_nonpartition[0x61] = 0; 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); m_nonpartition_contents.Add(NONPARTITION_DISCHEADER_ADDRESS, m_disc_header_nonpartition);
} }
@ -511,12 +554,14 @@ void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partiti
Write32(static_cast<u32>(partitions[i].type), offset_in_table, &m_partition_table); Write32(static_cast<u32>(partitions[i].type), offset_in_table, &m_partition_table);
offset_in_table += 4; 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(); const u64 data_size = partitions[i].partition.GetDataSize();
m_partitions.emplace(partition_address, std::move(partitions[i].partition)); 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( 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); partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull);
} }
m_data_size = partition_address; m_data_size = partition_address;
@ -526,7 +571,7 @@ void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partiti
// This function sets the header that's shortly before the start of the encrypted // 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 // 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) u64 partition_address)
{ {
constexpr u32 TICKET_OFFSET = 0x0; constexpr u32 TICKET_OFFSET = 0x0;
@ -536,10 +581,10 @@ void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& parti
constexpr u32 H3_OFFSET = 0x4000; constexpr u32 H3_OFFSET = 0x4000;
constexpr u32 H3_SIZE = 0x18000; 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, const u64 ticket_size = m_nonpartition_contents.CheckSizeAndAdd(
partition_root + "ticket.bin"); partition_address + TICKET_OFFSET, TICKET_SIZE, partition_root + "ticket.bin");
const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd( const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd(
partition_address + TMD_OFFSET, MAX_TMD_SIZE, partition_root + "tmd.bin"); 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"); partition_root + "h3.bin");
constexpr u32 PARTITION_HEADER_SIZE = 0x1c; 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); m_partition_headers.emplace_back(PARTITION_HEADER_SIZE);
std::vector<u8>& partition_header = m_partition_headers.back(); std::vector<u8>& partition_header = m_partition_headers.back();
Write32(static_cast<u32>(tmd_size), 0x0, &partition_header); Write32(static_cast<u32>(tmd_size), 0x0, &partition_header);
@ -565,6 +610,13 @@ void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& parti
Write32(static_cast<u32>(data_size >> 2), 0x18, &partition_header); Write32(static_cast<u32>(data_size >> 2), 0x18, &partition_header);
m_nonpartition_contents.Add(partition_address + TICKET_SIZE, partition_header); m_nonpartition_contents.Add(partition_address + TICKET_SIZE, partition_header);
std::vector<u8> 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, DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory,

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <array>
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <memory> #include <memory>
@ -16,6 +17,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "DiscIO/Blob.h" #include "DiscIO/Blob.h"
#include "DiscIO/WiiEncryptionCache.h"
namespace File namespace File
{ {
@ -27,16 +29,23 @@ namespace DiscIO
{ {
enum class PartitionType : u32; enum class PartitionType : u32;
class DirectoryBlobReader;
// Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself // Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself
bool ShouldHideFromGameList(const std::string& volume_path); bool ShouldHideFromGameList(const std::string& volume_path);
class DiscContent class DiscContent
{ {
public: public:
using ContentSource = std::variant<std::string, const u8*>; using ContentSource =
std::variant<std::string, // File
const u8*, // Memory
DirectoryBlobReader* // Partition (which one it is is determined by m_offset)
>;
DiscContent(u64 offset, u64 size, const std::string& path); DiscContent(u64 offset, u64 size, const std::string& path);
DiscContent(u64 offset, u64 size, const u8* data); 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 // Provided because it's convenient when searching for DiscContent in an std::set
explicit DiscContent(u64 offset); 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 std::string& path);
void Add(u64 offset, u64 size, const u8* data); 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, const std::string& path);
u64 CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path); u64 CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path);
@ -96,6 +106,9 @@ public:
const std::vector<u8>& GetHeader() const { return m_disc_header; } const std::vector<u8>& GetHeader() const { return m_disc_header; }
const DiscContentContainer& GetContents() const { return m_contents; } const DiscContentContainer& GetContents() const { return m_contents; }
const std::array<u8, VolumeWii::AES_KEY_SIZE>& GetKey() const { return m_key; }
void SetKey(std::array<u8, VolumeWii::AES_KEY_SIZE> key) { m_key = key; }
private: private:
void SetDiscHeaderAndDiscType(std::optional<bool> is_wii); void SetDiscHeaderAndDiscType(std::optional<bool> is_wii);
void SetBI2(); void SetBI2();
@ -120,6 +133,8 @@ private:
std::vector<u8> m_apploader; std::vector<u8> m_apploader;
std::vector<u8> m_fst_data; std::vector<u8> m_fst_data;
std::array<u8, VolumeWii::AES_KEY_SIZE> m_key;
std::string m_root_directory; std::string m_root_directory;
bool m_is_wii = false; bool m_is_wii = false;
// GameCube has no shift, Wii has 2 bit shift // GameCube has no shift, Wii has 2 bit shift
@ -130,6 +145,8 @@ private:
class DirectoryBlobReader : public BlobReader class DirectoryBlobReader : public BlobReader
{ {
friend DiscContent;
public: public:
static std::unique_ptr<DirectoryBlobReader> Create(const std::string& dol_path); static std::unique_ptr<DirectoryBlobReader> Create(const std::string& dol_path);
@ -141,7 +158,7 @@ public:
bool Read(u64 offset, u64 length, u8* buffer) override; bool Read(u64 offset, u64 length, u8* buffer) override;
bool SupportsReadWiiDecrypted() const 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; BlobType GetBlobType() const override;
u64 GetRawSize() const override; u64 GetRawSize() const override;
@ -163,11 +180,14 @@ private:
explicit DirectoryBlobReader(const std::string& game_partition_root, explicit DirectoryBlobReader(const std::string& game_partition_root,
const std::string& true_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<u8>& partition_header, void SetNonpartitionDiscHeader(const std::vector<u8>& partition_header,
const std::string& game_partition_root); const std::string& game_partition_root);
void SetWiiRegionData(const std::string& game_partition_root); void SetWiiRegionData(const std::string& game_partition_root);
void SetPartitions(std::vector<PartitionWithType>&& partitions); void SetPartitions(std::vector<PartitionWithType>&& partitions);
void SetPartitionHeader(const DirectoryBlobPartition& partition, u64 partition_address); void SetPartitionHeader(DirectoryBlobPartition* partition, u64 partition_address);
// For GameCube: // For GameCube:
DirectoryBlobPartition m_gamecube_pseudopartition; DirectoryBlobPartition m_gamecube_pseudopartition;
@ -175,8 +195,10 @@ private:
// For Wii: // For Wii:
DiscContentContainer m_nonpartition_contents; DiscContentContainer m_nonpartition_contents;
std::map<u64, DirectoryBlobPartition> m_partitions; std::map<u64, DirectoryBlobPartition> m_partitions;
WiiEncryptionCache m_encryption_cache;
bool m_is_wii; bool m_is_wii;
bool m_encrypted;
std::vector<u8> m_disc_header_nonpartition; std::vector<u8> m_disc_header_nonpartition;
std::vector<u8> m_partition_table; std::vector<u8> m_partition_table;

View File

@ -64,6 +64,7 @@
<ClCompile Include="VolumeWad.cpp" /> <ClCompile Include="VolumeWad.cpp" />
<ClCompile Include="VolumeWii.cpp" /> <ClCompile Include="VolumeWii.cpp" />
<ClCompile Include="WbfsBlob.cpp" /> <ClCompile Include="WbfsBlob.cpp" />
<ClCompile Include="WiiEncryptionCache.cpp" />
<ClCompile Include="WiiSaveBanner.cpp" /> <ClCompile Include="WiiSaveBanner.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -87,6 +88,7 @@
<ClInclude Include="VolumeWad.h" /> <ClInclude Include="VolumeWad.h" />
<ClInclude Include="VolumeWii.h" /> <ClInclude Include="VolumeWii.h" />
<ClInclude Include="WbfsBlob.h" /> <ClInclude Include="WbfsBlob.h" />
<ClInclude Include="WiiEncryptionCache.h" />
<ClInclude Include="WiiSaveBanner.h" /> <ClInclude Include="WiiSaveBanner.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -84,6 +84,9 @@
<ClCompile Include="VolumeVerifier.cpp"> <ClCompile Include="VolumeVerifier.cpp">
<Filter>Volume</Filter> <Filter>Volume</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="WiiEncryptionCache.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="DiscScrubber.h"> <ClInclude Include="DiscScrubber.h">
@ -149,6 +152,9 @@
<ClInclude Include="VolumeVerifier.h"> <ClInclude Include="VolumeVerifier.h">
<Filter>Volume</Filter> <Filter>Volume</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="WiiEncryptionCache.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Text Include="CMakeLists.txt" /> <Text Include="CMakeLists.txt" />

View File

@ -8,15 +8,19 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <future>
#include <map> #include <map>
#include <mbedtls/aes.h>
#include <mbedtls/sha1.h>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <thread>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <mbedtls/aes.h>
#include <mbedtls/sha1.h>
#include "Common/Align.h"
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -126,7 +130,7 @@ VolumeWii::VolumeWii(std::unique_ptr<BlobReader> reader)
const IOS::ES::TicketReader& ticket = *m_partitions[partition].ticket; const IOS::ES::TicketReader& ticket = *m_partitions[partition].ticket;
if (!ticket.IsValid()) if (!ticket.IsValid())
return nullptr; return nullptr;
const std::array<u8, 16> key = ticket.GetTitleKey(); const std::array<u8, AES_KEY_SIZE> key = ticket.GetTitleKey();
std::unique_ptr<mbedtls_aes_context> aes_context = std::make_unique<mbedtls_aes_context>(); std::unique_ptr<mbedtls_aes_context> aes_context = std::make_unique<mbedtls_aes_context>();
mbedtls_aes_setkey_dec(aes_context.get(), key.data(), 128); mbedtls_aes_setkey_dec(aes_context.get(), key.data(), 128);
return aes_context; return aes_context;
@ -162,14 +166,17 @@ bool VolumeWii::Read(u64 offset, u64 length, u8* buffer, const Partition& partit
if (partition == PARTITION_NONE) if (partition == PARTITION_NONE)
return m_reader->Read(offset, length, buffer); 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); auto it = m_partitions.find(partition);
if (it == m_partitions.end()) if (it == m_partitions.end())
return false; return false;
const PartitionDetails& partition_details = it->second; 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) if (!m_encrypted)
{ {
return m_reader->Read(partition.offset + *partition_details.data_offset + offset, length, 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<u8>& encr
return false; return false;
const PartitionDetails& partition_details = it->second; const PartitionDetails& partition_details = it->second;
constexpr size_t SHA1_SIZE = 20; if (block_index / BLOCKS_PER_GROUP * SHA1_SIZE >= partition_details.h3_table->size())
if (block_index / 64 * SHA1_SIZE >= partition_details.h3_table->size())
return false; return false;
mbedtls_aes_context* aes_context = partition_details.key->get(); mbedtls_aes_context* aes_context = partition_details.key->get();
if (!aes_context) if (!aes_context)
return false; return false;
u8 cluster_metadata[BLOCK_HEADER_SIZE]; HashBlock hashes;
u8 iv[16] = {0}; u8 iv[16] = {0};
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_HEADER_SIZE, iv, mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(HashBlock), iv,
encrypted_data.data(), cluster_metadata); encrypted_data.data(), reinterpret_cast<u8*>(&hashes));
u8 cluster_data[BLOCK_DATA_SIZE]; u8 cluster_data[BLOCK_DATA_SIZE];
std::memcpy(iv, encrypted_data.data() + 0x3D0, 16); std::memcpy(iv, encrypted_data.data() + 0x3D0, 16);
mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, BLOCK_DATA_SIZE, iv, mbedtls_aes_crypt_cbc(aes_context, MBEDTLS_AES_DECRYPT, sizeof(cluster_data), iv,
encrypted_data.data() + BLOCK_HEADER_SIZE, cluster_data); encrypted_data.data() + sizeof(HashBlock), cluster_data);
for (u32 hash_index = 0; hash_index < 31; ++hash_index) for (u32 hash_index = 0; hash_index < 31; ++hash_index)
{ {
u8 h0_hash[SHA1_SIZE]; u8 h0_hash[SHA1_SIZE];
mbedtls_sha1_ret(cluster_data + hash_index * 0x400, 0x400, h0_hash); 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; return false;
} }
u8 h1_hash[SHA1_SIZE]; u8 h1_hash[SHA1_SIZE];
mbedtls_sha1_ret(cluster_metadata, SHA1_SIZE * 31, h1_hash); mbedtls_sha1_ret(reinterpret_cast<u8*>(hashes.h0), sizeof(hashes.h0), h1_hash);
if (memcmp(h1_hash, cluster_metadata + 0x280 + (block_index % 8) * SHA1_SIZE, SHA1_SIZE)) if (memcmp(h1_hash, hashes.h1[block_index % 8], SHA1_SIZE))
return false; return false;
u8 h2_hash[SHA1_SIZE]; u8 h2_hash[SHA1_SIZE];
mbedtls_sha1_ret(cluster_metadata + 0x280, SHA1_SIZE * 8, h2_hash); mbedtls_sha1_ret(reinterpret_cast<u8*>(hashes.h1), sizeof(hashes.h1), h2_hash);
if (memcmp(h2_hash, cluster_metadata + 0x340 + (block_index / 8 % 8) * SHA1_SIZE, SHA1_SIZE)) if (memcmp(h2_hash, hashes.h2[block_index / 8 % 8], SHA1_SIZE))
return false; return false;
u8 h3_hash[SHA1_SIZE]; u8 h3_hash[SHA1_SIZE];
mbedtls_sha1_ret(cluster_metadata + 0x340, SHA1_SIZE * 8, h3_hash); mbedtls_sha1_ret(reinterpret_cast<u8*>(hashes.h2), sizeof(hashes.h2), h3_hash);
if (memcmp(h3_hash, partition_details.h3_table->data() + block_index / 64 * SHA1_SIZE, SHA1_SIZE)) if (memcmp(h3_hash, partition_details.h3_table->data() + block_index / 64 * SHA1_SIZE, SHA1_SIZE))
return false; return false;
@ -521,4 +527,139 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const Partition& partition)
return CheckBlockIntegrity(block_index, cluster, 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)
{
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;
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<u8*>(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<u8*>(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<unsigned int>(1, std::thread::hardware_concurrency()));
std::vector<std::future<void>> 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<u8*>(&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<void>& future : encryption_futures)
future.get();
return true;
}
} // namespace DiscIO } // namespace DiscIO

View File

@ -4,13 +4,15 @@
#pragma once #pragma once
#include <array>
#include <map> #include <map>
#include <mbedtls/aes.h>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector> #include <vector>
#include <mbedtls/aes.h>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Lazy.h" #include "Common/Lazy.h"
#include "Core/IOS/ES/Formats.h" #include "Core/IOS/ES/Formats.h"
@ -30,6 +32,31 @@ enum class Platform;
class VolumeWii : public VolumeDisc class VolumeWii : public VolumeDisc
{ {
public: 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<BlobReader> reader); VolumeWii(std::unique_ptr<BlobReader> reader);
~VolumeWii(); ~VolumeWii();
bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const override; bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const override;
@ -69,11 +96,9 @@ public:
bool IsSizeAccurate() const override; bool IsSizeAccurate() const override;
u64 GetRawSize() const override; u64 GetRawSize() const override;
static constexpr unsigned int H3_TABLE_SIZE = 0x18000; static bool EncryptGroup(u64 offset, u64 partition_data_offset, u64 partition_data_decrypted_size,
const std::array<u8, AES_KEY_SIZE>& key, BlobReader* blob,
static constexpr unsigned int BLOCK_HEADER_SIZE = 0x0400; std::array<u8, GROUP_TOTAL_SIZE>* out);
static constexpr unsigned int BLOCK_DATA_SIZE = 0x7C00;
static constexpr unsigned int BLOCK_TOTAL_SIZE = BLOCK_HEADER_SIZE + BLOCK_DATA_SIZE;
protected: protected:
u32 GetOffsetShift() const override { return 2; } u32 GetOffsetShift() const override { return 2; }

View File

@ -0,0 +1,80 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DiscIO/WiiEncryptionCache.h"
#include <array>
#include <cstring>
#include <limits>
#include <memory>
#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<u8, VolumeWii::GROUP_TOTAL_SIZE>*
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<std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>>();
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<u64>::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<u8, VolumeWii::GROUP_TOTAL_SIZE>* 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

View File

@ -0,0 +1,47 @@
// Copyright 2020 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <limits>
#include <memory>
#include "Common/CommonTypes.h"
#include "DiscIO/VolumeWii.h"
namespace DiscIO
{
class BlobReader;
class WiiEncryptionCache
{
public:
using Key = std::array<u8, VolumeWii::AES_KEY_SIZE>;
// 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<u8, VolumeWii::GROUP_TOTAL_SIZE>* 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<std::array<u8, VolumeWii::GROUP_TOTAL_SIZE>> m_cache;
u64 m_cached_offset = std::numeric_limits<u64>::max();
};
} // namespace DiscIO