DiscIO: Implement re-encryption of Wii partition data
This commit is contained in:
parent
a4c7100bcc
commit
319c508978
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -403,6 +427,21 @@ bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer,
|
||||||
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;
|
||||||
|
@ -440,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,13 +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 + PARTITION_DATA_OFFSET,
|
m_partitions.emplace(partition_address + PARTITION_DATA_OFFSET,
|
||||||
std::move(partitions[i].partition));
|
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;
|
||||||
|
@ -528,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;
|
||||||
|
@ -538,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");
|
||||||
|
@ -555,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);
|
||||||
|
@ -567,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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -9,14 +9,15 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#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 <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mbedtls/aes.h>
|
||||||
|
#include <mbedtls/sha1.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 +127,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;
|
||||||
|
@ -465,8 +466,7 @@ 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();
|
||||||
|
@ -524,4 +524,93 @@ 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);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < BLOCKS_PER_GROUP; ++i)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unencrypted_data[i].fill(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::array<u8, BLOCK_HEADER_SIZE>> unencrypted_hashes(BLOCKS_PER_GROUP);
|
||||||
|
|
||||||
|
// H0 hashes
|
||||||
|
for (size_t i = 0; i < BLOCKS_PER_GROUP; ++i)
|
||||||
|
{
|
||||||
|
for (u32 j = 0; j < 31; ++j)
|
||||||
|
{
|
||||||
|
mbedtls_sha1_ret(unencrypted_data[i].data() + j * 0x400, 0x400,
|
||||||
|
unencrypted_hashes[i].data() + j * SHA1_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(unencrypted_hashes[i].data() + 0x26C, 0, 0x14);
|
||||||
|
}
|
||||||
|
|
||||||
|
// H1 hashes
|
||||||
|
for (size_t i = 0; i < BLOCKS_PER_GROUP / 8; ++i)
|
||||||
|
{
|
||||||
|
for (u32 j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
mbedtls_sha1_ret(unencrypted_hashes[i * 8 + j].data(), 0x26C,
|
||||||
|
unencrypted_hashes[i * 8].data() + 0x280 + j * SHA1_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(unencrypted_hashes[i * 8].data() + 0x320, 0, 0x20);
|
||||||
|
|
||||||
|
for (u32 j = 1; j < 8; ++j)
|
||||||
|
{
|
||||||
|
std::memcpy(unencrypted_hashes[i * 8 + j].data() + 0x280,
|
||||||
|
unencrypted_hashes[i * 8].data() + 0x280, 0xC0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// H2 hashes
|
||||||
|
{
|
||||||
|
for (u32 i = 0; i < BLOCKS_PER_GROUP / 8; ++i)
|
||||||
|
{
|
||||||
|
mbedtls_sha1_ret(unencrypted_hashes[i * 8].data() + 0x280, 0xA0,
|
||||||
|
unencrypted_hashes[0].data() + 0x340 + i * SHA1_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(unencrypted_hashes[0].data() + 0x3E0, 0, 0x20);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < BLOCKS_PER_GROUP; ++i)
|
||||||
|
std::memcpy(unencrypted_hashes[i].data() + 0x340, unencrypted_hashes[0].data() + 0x340, 0xC0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mbedtls_aes_context aes_context;
|
||||||
|
mbedtls_aes_setkey_enc(&aes_context, key.data(), 128);
|
||||||
|
|
||||||
|
// Encryption
|
||||||
|
for (size_t i = 0; i < BLOCKS_PER_GROUP; ++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,
|
||||||
|
unencrypted_hashes[i].data(), 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -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,20 @@ 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;
|
||||||
|
|
||||||
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 +85,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; }
|
||||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue