diff options
Diffstat (limited to 'Source/Core/DiscIO/DirectoryBlob.cpp')
| -rw-r--r-- | Source/Core/DiscIO/DirectoryBlob.cpp | 609 |
1 files changed, 469 insertions, 140 deletions
diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp index c8352c79e1..80bd173ae0 100644 --- a/Source/Core/DiscIO/DirectoryBlob.cpp +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -28,6 +28,7 @@ #include "Core/IOS/ES/Formats.h" #include "DiscIO/Blob.h" #include "DiscIO/DiscUtils.h" +#include "DiscIO/VolumeDisc.h" #include "DiscIO/VolumeWii.h" #include "DiscIO/WiiEncryptionCache.h" @@ -40,9 +41,7 @@ static size_t ReadFileToVector(const std::string& path, std::vector<u8>* vector) static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer); static void Write32(u32 data, u32 offset, std::vector<u8>* buffer); -static u32 ComputeNameSize(const File::FSTEntry& parent_entry); static std::string ASCIIToUppercase(std::string str); -static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry* parent_entry); enum class PartitionType : u32 { @@ -61,18 +60,8 @@ constexpr u8 ENTRY_SIZE = 0x0c; constexpr u8 FILE_ENTRY = 0; constexpr u8 DIRECTORY_ENTRY = 1; -DiscContent::DiscContent(u64 offset, u64 size, const std::string& path) - : m_offset(offset), m_size(size), m_content_source(path) -{ -} - -DiscContent::DiscContent(u64 offset, u64 size, const u8* data) - : m_offset(offset), m_size(size), m_content_source(data) -{ -} - -DiscContent::DiscContent(u64 offset, u64 size, DirectoryBlobReader* blob) - : m_offset(offset), m_size(size), m_content_source(blob) +DiscContent::DiscContent(u64 offset, u64 size, ContentSource source) + : m_offset(offset), m_size(size), m_content_source(std::move(source)) { } @@ -107,27 +96,51 @@ bool DiscContent::Read(u64* offset, u64* length, u8** buffer) const { const u64 bytes_to_read = std::min(m_size - offset_in_content, *length); - if (std::holds_alternative<std::string>(m_content_source)) + if (std::holds_alternative<ContentFile>(m_content_source)) { - File::IOFile file(std::get<std::string>(m_content_source), "rb"); - if (!file.Seek(offset_in_content, SEEK_SET) || !file.ReadBytes(*buffer, bytes_to_read)) + const auto& content = std::get<ContentFile>(m_content_source); + File::IOFile file(content.m_filename, "rb"); + if (!file.Seek(content.m_offset + offset_in_content, SEEK_SET) || + !file.ReadBytes(*buffer, bytes_to_read)) + { return false; + } } 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; std::copy(content_pointer, content_pointer + bytes_to_read, *buffer); } - else + else if (std::holds_alternative<ContentPartition>(m_content_source)) { - DirectoryBlobReader* blob = std::get<DirectoryBlobReader*>(m_content_source); + const auto& content = std::get<ContentPartition>(m_content_source); + DirectoryBlobReader* blob = content.m_reader; 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)) + if (!blob->EncryptPartitionData(content.m_offset + offset_in_content, bytes_to_read, *buffer, + content.m_partition_data_offset, decrypted_size)) + { + return false; + } + } + else if (std::holds_alternative<ContentVolume>(m_content_source)) + { + const auto& source = std::get<ContentVolume>(m_content_source); + if (!source.m_volume->Read(source.m_offset + offset_in_content, bytes_to_read, *buffer, + source.m_partition)) { return false; } } + else if (std::holds_alternative<ContentFixedByte>(m_content_source)) + { + const ContentFixedByte& source = std::get<ContentFixedByte>(m_content_source); + std::fill_n(*buffer, bytes_to_read, source.m_byte); + } + else + { + PanicAlertFmt("DirectoryBlob: Invalid content source in DiscContent."); + return false; + } *length -= bytes_to_read; *buffer += bytes_to_read; @@ -137,35 +150,23 @@ bool DiscContent::Read(u64* offset, u64* length, u8** buffer) const return true; } -void DiscContentContainer::Add(u64 offset, u64 size, const std::string& path) -{ - if (size != 0) - m_contents.emplace(offset, size, path); -} - -void DiscContentContainer::Add(u64 offset, u64 size, const u8* data) -{ - if (size != 0) - m_contents.emplace(offset, size, data); -} - -void DiscContentContainer::Add(u64 offset, u64 size, DirectoryBlobReader* blob) +void DiscContentContainer::Add(u64 offset, u64 size, ContentSource source) { if (size != 0) - m_contents.emplace(offset, size, blob); + m_contents.emplace(offset, size, std::move(source)); } u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, const std::string& path) { const u64 size = File::GetSize(path); - Add(offset, size, path); + Add(offset, size, ContentFile{path, 0}); return size; } u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path) { const u64 size = std::min(File::GetSize(path), max_size); - Add(offset, size, path); + Add(offset, size, ContentFile{path, 0}); return size; } @@ -355,6 +356,18 @@ std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(const std::stri return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(partition_root, true_root)); } +std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create( + std::unique_ptr<DiscIO::VolumeDisc> volume, + const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>& + fst_callback) +{ + if (!volume) + return nullptr; + + return std::unique_ptr<DirectoryBlobReader>( + new DirectoryBlobReader(std::move(volume), fst_callback)); +} + DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, const std::string& true_root) : m_encryption_cache(this) @@ -370,8 +383,8 @@ DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, } else { - SetNonpartitionDiscHeader(game_partition.GetHeader(), game_partition_root); - SetWiiRegionData(game_partition_root); + SetNonpartitionDiscHeaderFromFile(game_partition.GetHeader(), game_partition_root); + SetWiiRegionDataFromFile(game_partition_root); std::vector<PartitionWithType> partitions; partitions.emplace_back(std::move(game_partition), PartitionType::Game); @@ -399,6 +412,62 @@ DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, } } +DirectoryBlobReader::DirectoryBlobReader( + std::unique_ptr<DiscIO::VolumeDisc> volume, + const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>& + fst_callback) + : m_encryption_cache(this), m_wrapped_volume(std::move(volume)) +{ + DirectoryBlobPartition game_partition( + m_wrapped_volume.get(), m_wrapped_volume->GetGamePartition(), std::nullopt, fst_callback); + m_is_wii = game_partition.IsWii(); + + if (!m_is_wii) + { + m_gamecube_pseudopartition = std::move(game_partition); + m_data_size = m_gamecube_pseudopartition.GetDataSize(); + m_encrypted = false; + } + else + { + std::vector<u8> header_bin(WII_NONPARTITION_DISCHEADER_SIZE); + if (!m_wrapped_volume->Read(WII_NONPARTITION_DISCHEADER_ADDRESS, + WII_NONPARTITION_DISCHEADER_SIZE, header_bin.data(), + PARTITION_NONE)) + { + header_bin.clear(); + } + SetNonpartitionDiscHeader(game_partition.GetHeader(), std::move(header_bin)); + + std::vector<u8> wii_region_data(WII_REGION_DATA_SIZE); + if (!m_wrapped_volume->Read(WII_REGION_DATA_ADDRESS, WII_REGION_DATA_SIZE, + wii_region_data.data(), PARTITION_NONE)) + { + wii_region_data.clear(); + } + SetWiiRegionData(wii_region_data, "volume"); + + std::vector<PartitionWithType> partitions; + partitions.emplace_back(std::move(game_partition), PartitionType::Game); + + for (Partition partition : m_wrapped_volume->GetPartitions()) + { + if (partition == m_wrapped_volume->GetGamePartition()) + continue; + + auto type = m_wrapped_volume->GetPartitionType(partition); + if (type) + { + partitions.emplace_back( + DirectoryBlobPartition(m_wrapped_volume.get(), partition, m_is_wii, nullptr), + static_cast<PartitionType>(*type)); + } + } + + SetPartitions(std::move(partitions)); + } +} + bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer) { if (offset + length > m_data_size) @@ -468,44 +537,70 @@ u64 DirectoryBlobReader::GetDataSize() const return m_data_size; } +void DirectoryBlobReader::SetNonpartitionDiscHeaderFromFile(const std::vector<u8>& partition_header, + const std::string& game_partition_root) +{ + std::vector<u8> header_bin(WII_NONPARTITION_DISCHEADER_SIZE); + const size_t header_bin_bytes_read = + ReadFileToVector(game_partition_root + "disc/header.bin", &header_bin); + header_bin.resize(header_bin_bytes_read); + SetNonpartitionDiscHeader(partition_header, std::move(header_bin)); +} + void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector<u8>& partition_header, - const std::string& game_partition_root) + std::vector<u8> header_bin) { + const size_t header_bin_size = header_bin.size(); + m_disc_header_nonpartition = std::move(header_bin); m_disc_header_nonpartition.resize(WII_NONPARTITION_DISCHEADER_SIZE); - const size_t header_bin_bytes_read = - ReadFileToVector(game_partition_root + "disc/header.bin", &m_disc_header_nonpartition); // If header.bin is missing or smaller than expected, use the content of sys/boot.bin instead - std::copy(partition_header.data() + header_bin_bytes_read, - partition_header.data() + m_disc_header_nonpartition.size(), - m_disc_header_nonpartition.data() + header_bin_bytes_read); + if (header_bin_size < m_disc_header_nonpartition.size()) + { + std::copy(partition_header.data() + header_bin_size, + partition_header.data() + m_disc_header_nonpartition.size(), + m_disc_header_nonpartition.data() + header_bin_size); + } // 0x60 and 0x61 are the only differences between the partition and non-partition headers - if (header_bin_bytes_read < 0x60) + if (header_bin_size < 0x60) m_disc_header_nonpartition[0x60] = 0; - if (header_bin_bytes_read < 0x61) + if (header_bin_size < 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(WII_NONPARTITION_DISCHEADER_ADDRESS, m_disc_header_nonpartition); + m_nonpartition_contents.AddReference(WII_NONPARTITION_DISCHEADER_ADDRESS, + m_disc_header_nonpartition); } -void DirectoryBlobReader::SetWiiRegionData(const std::string& game_partition_root) +void DirectoryBlobReader::SetWiiRegionDataFromFile(const std::string& game_partition_root) +{ + std::vector<u8> wii_region_data(WII_REGION_DATA_SIZE); + const std::string region_bin_path = game_partition_root + "disc/region.bin"; + const size_t bytes_read = ReadFileToVector(region_bin_path, &wii_region_data); + wii_region_data.resize(bytes_read); + SetWiiRegionData(wii_region_data, region_bin_path); +} + +void DirectoryBlobReader::SetWiiRegionData(const std::vector<u8>& wii_region_data, + const std::string& log_path) { m_wii_region_data.resize(0x10, 0x00); - m_wii_region_data.resize(0x20, 0x80); + m_wii_region_data.resize(WII_REGION_DATA_SIZE, 0x80); Write32(INVALID_REGION, 0, &m_wii_region_data); - const std::string region_bin_path = game_partition_root + "disc/region.bin"; - const size_t bytes_read = ReadFileToVector(region_bin_path, &m_wii_region_data); - if (bytes_read < 0x4) - ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", region_bin_path); - else if (bytes_read < 0x20) - ERROR_LOG_FMT(DISCIO, "Couldn't read age ratings from {}", region_bin_path); + std::copy_n(wii_region_data.begin(), + std::min<size_t>(wii_region_data.size(), WII_REGION_DATA_SIZE), + m_wii_region_data.begin()); - m_nonpartition_contents.Add(WII_REGION_DATA_ADDRESS, m_wii_region_data); + if (wii_region_data.size() < 0x4) + ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", log_path); + else if (wii_region_data.size() < 0x20) + ERROR_LOG_FMT(DISCIO, "Couldn't read age ratings from {}", log_path); + + m_nonpartition_contents.AddReference(WII_REGION_DATA_ADDRESS, m_wii_region_data); } void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partitions) @@ -562,16 +657,17 @@ void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partiti SetPartitionHeader(&partitions[i].partition, partition_address); 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 partition_data_offset = partition_address + PARTITION_DATA_OFFSET; + m_partitions.emplace(partition_data_offset, std::move(partitions[i].partition)); + m_nonpartition_contents.Add(partition_data_offset, data_size, + ContentPartition{this, 0, partition_data_offset}); const u64 unaligned_next_partition_address = VolumeWii::EncryptedPartitionOffsetToRawOffset( data_size, Partition(partition_address), PARTITION_DATA_OFFSET); partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull); } m_data_size = partition_address; - m_nonpartition_contents.Add(PARTITION_TABLE_ADDRESS, m_partition_table); + m_nonpartition_contents.AddReference(PARTITION_TABLE_ADDRESS, m_partition_table); } // This function sets the header that's shortly before the start of the encrypted @@ -582,27 +678,88 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, constexpr u32 TMD_OFFSET = 0x2c0; constexpr u32 H3_OFFSET = 0x4000; + const std::optional<DiscIO::Partition>& wrapped_partition = partition->GetWrappedPartition(); const std::string& partition_root = partition->GetRootDirectory(); - const u64 ticket_size = m_nonpartition_contents.CheckSizeAndAdd( - partition_address + WII_PARTITION_TICKET_ADDRESS, WII_PARTITION_TICKET_SIZE, - partition_root + "ticket.bin"); + u64 ticket_size; + if (wrapped_partition) + { + const auto& ticket = m_wrapped_volume->GetTicket(*wrapped_partition).GetBytes(); + auto& new_ticket = m_extra_data.emplace_back(ticket); + if (new_ticket.size() > WII_PARTITION_TICKET_SIZE) + new_ticket.resize(WII_PARTITION_TICKET_SIZE); + ticket_size = new_ticket.size(); + m_nonpartition_contents.AddReference(partition_address + WII_PARTITION_TICKET_ADDRESS, + new_ticket); + } + else + { + ticket_size = m_nonpartition_contents.CheckSizeAndAdd( + partition_address + WII_PARTITION_TICKET_ADDRESS, WII_PARTITION_TICKET_SIZE, + partition_root + "ticket.bin"); + } - const u64 tmd_size = m_nonpartition_contents.CheckSizeAndAdd( - partition_address + TMD_OFFSET, IOS::ES::MAX_TMD_SIZE, partition_root + "tmd.bin"); + u64 tmd_size; + if (wrapped_partition) + { + const auto& tmd = m_wrapped_volume->GetTMD(*wrapped_partition).GetBytes(); + auto& new_tmd = m_extra_data.emplace_back(tmd); + if (new_tmd.size() > IOS::ES::MAX_TMD_SIZE) + new_tmd.resize(IOS::ES::MAX_TMD_SIZE); + tmd_size = new_tmd.size(); + m_nonpartition_contents.AddReference(partition_address + TMD_OFFSET, new_tmd); + } + else + { + tmd_size = m_nonpartition_contents.CheckSizeAndAdd( + partition_address + TMD_OFFSET, IOS::ES::MAX_TMD_SIZE, partition_root + "tmd.bin"); + } const u64 cert_offset = Common::AlignUp(TMD_OFFSET + tmd_size, 0x20ull); const u64 max_cert_size = H3_OFFSET - cert_offset; - const u64 cert_size = m_nonpartition_contents.CheckSizeAndAdd( - partition_address + cert_offset, max_cert_size, partition_root + "cert.bin"); - m_nonpartition_contents.CheckSizeAndAdd(partition_address + H3_OFFSET, WII_PARTITION_H3_SIZE, - partition_root + "h3.bin"); + u64 cert_size; + if (wrapped_partition) + { + const auto& cert = m_wrapped_volume->GetCertificateChain(*wrapped_partition); + auto& new_cert = m_extra_data.emplace_back(cert); + if (new_cert.size() > max_cert_size) + new_cert.resize(max_cert_size); + cert_size = new_cert.size(); + m_nonpartition_contents.AddReference(partition_address + cert_offset, new_cert); + } + else + { + cert_size = m_nonpartition_contents.CheckSizeAndAdd(partition_address + cert_offset, + max_cert_size, partition_root + "cert.bin"); + } + + if (wrapped_partition) + { + if (m_wrapped_volume->IsEncryptedAndHashed()) + { + const std::optional<u64> offset = m_wrapped_volume->ReadSwappedAndShifted( + wrapped_partition->offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE); + if (offset) + { + auto& new_h3 = m_extra_data.emplace_back(WII_PARTITION_H3_SIZE); + if (m_wrapped_volume->Read(wrapped_partition->offset + *offset, new_h3.size(), + new_h3.data(), PARTITION_NONE)) + { + m_nonpartition_contents.AddReference(partition_address + H3_OFFSET, new_h3); + } + } + } + } + else + { + m_nonpartition_contents.CheckSizeAndAdd(partition_address + H3_OFFSET, WII_PARTITION_H3_SIZE, + partition_root + "h3.bin"); + } constexpr u32 PARTITION_HEADER_SIZE = 0x1c; const u64 data_size = Common::AlignUp(partition->GetDataSize(), 0x7c00) / 0x7c00 * 0x8000; - m_partition_headers.emplace_back(PARTITION_HEADER_SIZE); - std::vector<u8>& partition_header = m_partition_headers.back(); + std::vector<u8>& partition_header = m_extra_data.emplace_back(PARTITION_HEADER_SIZE); Write32(static_cast<u32>(tmd_size), 0x0, &partition_header); Write32(TMD_OFFSET >> 2, 0x4, &partition_header); Write32(static_cast<u32>(cert_size), 0x8, &partition_header); @@ -611,7 +768,8 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, Write32(PARTITION_DATA_OFFSET >> 2, 0x14, &partition_header); Write32(static_cast<u32>(data_size >> 2), 0x18, &partition_header); - m_nonpartition_contents.Add(partition_address + WII_PARTITION_TICKET_SIZE, partition_header); + m_nonpartition_contents.AddReference(partition_address + WII_PARTITION_TICKET_SIZE, + partition_header); std::vector<u8> ticket_buffer(ticket_size); m_nonpartition_contents.Read(partition_address + WII_PARTITION_TICKET_ADDRESS, ticket_size, @@ -621,24 +779,116 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition, partition->SetKey(ticket.GetTitleKey()); } +static void GenerateBuilderNodesFromFileSystem(const DiscIO::VolumeDisc& volume, + const DiscIO::Partition& partition, + std::vector<FSTBuilderNode>* nodes, + const FileInfo& parent_info) +{ + for (const FileInfo& file_info : parent_info) + { + if (file_info.IsDirectory()) + { + std::vector<FSTBuilderNode> child_nodes; + GenerateBuilderNodesFromFileSystem(volume, partition, &child_nodes, file_info); + nodes->emplace_back(FSTBuilderNode{file_info.GetName(), file_info.GetTotalChildren(), + std::move(child_nodes)}); + } + else + { + std::vector<BuilderContentSource> source; + source.emplace_back(BuilderContentSource{ + 0, file_info.GetSize(), ContentVolume{file_info.GetOffset(), &volume, partition}}); + nodes->emplace_back( + FSTBuilderNode{file_info.GetName(), file_info.GetSize(), std::move(source)}); + } + } +} + DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory, std::optional<bool> is_wii) : m_root_directory(root_directory) { - SetDiscHeaderAndDiscType(is_wii); - SetBI2(); - BuildFST(SetDOL(SetApploader())); + SetDiscHeaderFromFile(m_root_directory + "sys/boot.bin"); + SetDiscType(is_wii); + SetBI2FromFile(m_root_directory + "sys/bi2.bin"); + const u64 dol_address = SetApploaderFromFile(m_root_directory + "sys/apploader.img"); + const u64 fst_address = SetDOLFromFile(m_root_directory + "sys/main.dol", dol_address); + BuildFSTFromFolder(m_root_directory + "files/", fst_address); +} + +DirectoryBlobPartition::DirectoryBlobPartition( + DiscIO::VolumeDisc* volume, const DiscIO::Partition& partition, std::optional<bool> is_wii, + const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>& + fst_callback) + : m_wrapped_partition(partition) +{ + std::vector<u8> disc_header(DISCHEADER_SIZE); + if (!volume->Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header.data(), partition)) + disc_header.clear(); + SetDiscHeader(std::move(disc_header)); + SetDiscType(is_wii); + + std::vector<u8> bi2(BI2_SIZE); + if (!volume->Read(BI2_ADDRESS, BI2_SIZE, bi2.data(), partition)) + bi2.clear(); + SetBI2(std::move(bi2)); + + std::vector<u8> apploader; + const auto apploader_size = GetApploaderSize(*volume, partition); + if (apploader_size) + { + apploader.resize(*apploader_size); + if (!volume->Read(APPLOADER_ADDRESS, *apploader_size, apploader.data(), partition)) + apploader.clear(); + } + const u64 new_dol_address = SetApploader(apploader, "apploader"); + + FSTBuilderNode dol_node{"main.dol", 0, {}}; + const auto dol_offset = GetBootDOLOffset(*volume, partition); + if (dol_offset) + { + const auto dol_size = GetBootDOLSize(*volume, partition, *dol_offset); + if (dol_size) + { + std::vector<BuilderContentSource> dol_contents; + dol_contents.emplace_back( + BuilderContentSource{0, *dol_size, ContentVolume{*dol_offset, volume, partition}}); + dol_node.m_size = *dol_size; + dol_node.m_content = std::move(dol_contents); + } + } + + std::vector<FSTBuilderNode> nodes; + + const FileSystem* fs = volume->GetFileSystem(partition); + if (fs && fs->IsValid()) + GenerateBuilderNodesFromFileSystem(*volume, partition, &nodes, fs->GetRoot()); + + if (fst_callback) + fst_callback(&nodes, &dol_node); + + const u64 new_fst_address = SetDOL(std::move(dol_node), new_dol_address); + BuildFST(std::move(nodes), new_fst_address); } -void DirectoryBlobPartition::SetDiscHeaderAndDiscType(std::optional<bool> is_wii) +void DirectoryBlobPartition::SetDiscHeaderFromFile(const std::string& boot_bin_path) { m_disc_header.resize(DISCHEADER_SIZE); - const std::string boot_bin_path = m_root_directory + "sys/boot.bin"; if (ReadFileToVector(boot_bin_path, &m_disc_header) < 0x20) ERROR_LOG_FMT(DISCIO, "{} doesn't exist or is too small", boot_bin_path); - m_contents.Add(DISCHEADER_ADDRESS, m_disc_header); + m_contents.AddReference(DISCHEADER_ADDRESS, m_disc_header); +} + +void DirectoryBlobPartition::SetDiscHeader(std::vector<u8> boot_bin) +{ + m_disc_header = std::move(boot_bin); + m_disc_header.resize(DISCHEADER_SIZE); + m_contents.AddReference(DISCHEADER_ADDRESS, m_disc_header); +} +void DirectoryBlobPartition::SetDiscType(std::optional<bool> is_wii) +{ if (is_wii.has_value()) { m_is_wii = *is_wii; @@ -648,44 +898,64 @@ void DirectoryBlobPartition::SetDiscHeaderAndDiscType(std::optional<bool> is_wii m_is_wii = Common::swap32(&m_disc_header[0x18]) == WII_DISC_MAGIC; const bool is_gc = Common::swap32(&m_disc_header[0x1c]) == GAMECUBE_DISC_MAGIC; if (m_is_wii == is_gc) - ERROR_LOG_FMT(DISCIO, "Couldn't detect disc type based on {}", boot_bin_path); + { + ERROR_LOG_FMT(DISCIO, "Couldn't detect disc type based on disc header; assuming {}", + m_is_wii ? "Wii" : "GameCube"); + } } m_address_shift = m_is_wii ? 2 : 0; } -void DirectoryBlobPartition::SetBI2() +void DirectoryBlobPartition::SetBI2FromFile(const std::string& bi2_path) { m_bi2.resize(BI2_SIZE); if (!m_is_wii) Write32(INVALID_REGION, 0x18, &m_bi2); - const std::string bi2_path = m_root_directory + "sys/bi2.bin"; const size_t bytes_read = ReadFileToVector(bi2_path, &m_bi2); if (!m_is_wii && bytes_read < 0x1C) ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", bi2_path); - m_contents.Add(BI2_ADDRESS, m_bi2); + m_contents.AddReference(BI2_ADDRESS, m_bi2); } -u64 DirectoryBlobPartition::SetApploader() +void DirectoryBlobPartition::SetBI2(std::vector<u8> bi2) { - bool success = false; + const size_t bi2_size = bi2.size(); + m_bi2 = std::move(bi2); + m_bi2.resize(BI2_SIZE); - const std::string path = m_root_directory + "sys/apploader.img"; + if (!m_is_wii && bi2_size < 0x1C) + Write32(INVALID_REGION, 0x18, &m_bi2); + + m_contents.AddReference(BI2_ADDRESS, m_bi2); +} + +u64 DirectoryBlobPartition::SetApploaderFromFile(const std::string& path) +{ File::IOFile file(path, "rb"); - m_apploader.resize(file.GetSize()); - if (m_apploader.size() < 0x20 || !file.ReadBytes(m_apploader.data(), m_apploader.size())) + std::vector<u8> apploader(file.GetSize()); + file.ReadBytes(apploader.data(), apploader.size()); + return SetApploader(std::move(apploader), path); +} + +u64 DirectoryBlobPartition::SetApploader(std::vector<u8> apploader, const std::string& log_path) +{ + bool success = false; + + m_apploader = std::move(apploader); + if (m_apploader.size() < 0x20) { - ERROR_LOG_FMT(DISCIO, "{} couldn't be accessed or is too small", path); + ERROR_LOG_FMT(DISCIO, "{} couldn't be accessed or is too small", log_path); } else { const size_t apploader_size = 0x20 + Common::swap32(*(u32*)&m_apploader[0x14]) + Common::swap32(*(u32*)&m_apploader[0x18]); if (apploader_size != m_apploader.size()) - ERROR_LOG_FMT(DISCIO, "{} is the wrong size... Is it really an apploader?", path); + ERROR_LOG_FMT(DISCIO, "{} is the wrong size... Is it really an apploader?", log_path); else success = true; } @@ -697,15 +967,15 @@ u64 DirectoryBlobPartition::SetApploader() Write32(static_cast<u32>(-1), 0x10, &m_apploader); } - m_contents.Add(APPLOADER_ADDRESS, m_apploader); + m_contents.AddReference(APPLOADER_ADDRESS, m_apploader); // Return DOL address, 32 byte aligned (plus 32 byte padding) return Common::AlignUp(APPLOADER_ADDRESS + m_apploader.size() + 0x20, 0x20ull); } -u64 DirectoryBlobPartition::SetDOL(u64 dol_address) +u64 DirectoryBlobPartition::SetDOLFromFile(const std::string& path, u64 dol_address) { - const u64 dol_size = m_contents.CheckSizeAndAdd(dol_address, m_root_directory + "sys/main.dol"); + const u64 dol_size = m_contents.CheckSizeAndAdd(dol_address, path); Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, &m_disc_header); @@ -713,16 +983,92 @@ u64 DirectoryBlobPartition::SetDOL(u64 dol_address) return Common::AlignUp(dol_address + dol_size + 0x20, 0x20ull); } -void DirectoryBlobPartition::BuildFST(u64 fst_address) +u64 DirectoryBlobPartition::SetDOL(FSTBuilderNode dol_node, u64 dol_address) +{ + for (auto& content : dol_node.GetFileContent()) + m_contents.Add(dol_address + content.m_offset, content.m_size, std::move(content.m_source)); + + Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, &m_disc_header); + + // Return FST address, 32 byte aligned (plus 32 byte padding) + return Common::AlignUp(dol_address + dol_node.m_size + 0x20, 0x20ull); +} + +static std::vector<FSTBuilderNode> ConvertFSTEntriesToBuilderNodes(const File::FSTEntry& parent) +{ + std::vector<FSTBuilderNode> nodes; + nodes.reserve(parent.children.size()); + for (const File::FSTEntry& entry : parent.children) + { + std::variant<std::vector<BuilderContentSource>, std::vector<FSTBuilderNode>> content; + if (entry.isDirectory) + { + content = ConvertFSTEntriesToBuilderNodes(entry); + } + else + { + content = + std::vector<BuilderContentSource>{{0, entry.size, ContentFile{entry.physicalName, 0}}}; + } + + nodes.emplace_back(FSTBuilderNode{entry.virtualName, entry.size, std::move(content)}); + } + return nodes; +} + +void DirectoryBlobPartition::BuildFSTFromFolder(const std::string& fst_root_path, u64 fst_address) +{ + auto nodes = ConvertFSTEntriesToBuilderNodes(File::ScanDirectoryTree(fst_root_path, true)); + BuildFST(std::move(nodes), fst_address); +} + +static void ConvertUTF8NamesToSHIFTJIS(std::vector<FSTBuilderNode>* fst) +{ + for (FSTBuilderNode& entry : *fst) + { + if (entry.IsFolder()) + ConvertUTF8NamesToSHIFTJIS(&entry.GetFolderContent()); + entry.m_filename = UTF8ToSHIFTJIS(entry.m_filename); + } +} + +static u32 ComputeNameSize(const std::vector<FSTBuilderNode>& files) +{ + u32 name_size = 0; + for (const FSTBuilderNode& entry : files) + { + if (entry.IsFolder()) + name_size += ComputeNameSize(entry.GetFolderContent()); + name_size += static_cast<u32>(entry.m_filename.length() + 1); + } + return name_size; +} + +static size_t RecalculateFolderSizes(std::vector<FSTBuilderNode>* fst) +{ + size_t size = 0; + for (FSTBuilderNode& entry : *fst) + { + ++size; + if (entry.IsFile()) + continue; + + entry.m_size = RecalculateFolderSizes(&entry.GetFolderContent()); + size += entry.m_size; + } + return size; +} + +void DirectoryBlobPartition::BuildFST(std::vector<FSTBuilderNode> root_nodes, u64 fst_address) { m_fst_data.clear(); - File::FSTEntry rootEntry = File::ScanDirectoryTree(m_root_directory + "files/", true); + ConvertUTF8NamesToSHIFTJIS(&root_nodes); - ConvertUTF8NamesToSHIFTJIS(&rootEntry); + u32 name_table_size = Common::AlignUp(ComputeNameSize(root_nodes), 1ull << m_address_shift); - u32 name_table_size = Common::AlignUp(ComputeNameSize(rootEntry), 1ull << m_address_shift); - u64 total_entries = rootEntry.size + 1; // The root entry itself isn't counted in rootEntry.size + // 1 extra for the root entry + u64 total_entries = RecalculateFolderSizes(&root_nodes) + 1; const u64 name_table_offset = total_entries * ENTRY_SIZE; m_fst_data.resize(name_table_offset + name_table_size); @@ -737,7 +1083,7 @@ void DirectoryBlobPartition::BuildFST(u64 fst_address) // write root entry WriteEntryData(&fst_offset, DIRECTORY_ENTRY, 0, 0, total_entries, m_address_shift); - WriteDirectory(rootEntry, &fst_offset, &name_offset, ¤t_data_address, root_offset, + WriteDirectory(&root_nodes, &fst_offset, &name_offset, ¤t_data_address, root_offset, name_table_offset); // overflow check, compare the aligned name offset with the aligned name table size @@ -748,7 +1094,7 @@ void DirectoryBlobPartition::BuildFST(u64 fst_address) Write32((u32)(m_fst_data.size() >> m_address_shift), 0x0428, &m_disc_header); Write32((u32)(m_fst_data.size() >> m_address_shift), 0x042c, &m_disc_header); - m_contents.Add(fst_address, m_fst_data); + m_contents.AddReference(fst_address, m_fst_data); m_data_size = current_data_address; } @@ -777,44 +1123,51 @@ void DirectoryBlobPartition::WriteEntryName(u32* name_offset, const std::string& *name_offset += (u32)(name.length() + 1); } -void DirectoryBlobPartition::WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, - u32* name_offset, u64* data_offset, +void DirectoryBlobPartition::WriteDirectory(std::vector<FSTBuilderNode>* parent_entries, + u32* fst_offset, u32* name_offset, u64* data_offset, u32 parent_entry_index, u64 name_table_offset) { - std::vector<File::FSTEntry> sorted_entries = parent_entry.children; + std::vector<FSTBuilderNode>& sorted_entries = *parent_entries; // Sort for determinism std::sort(sorted_entries.begin(), sorted_entries.end(), - [](const File::FSTEntry& one, const File::FSTEntry& two) { - const std::string one_upper = ASCIIToUppercase(one.virtualName); - const std::string two_upper = ASCIIToUppercase(two.virtualName); - return one_upper == two_upper ? one.virtualName < two.virtualName : + [](const FSTBuilderNode& one, const FSTBuilderNode& two) { + const std::string one_upper = ASCIIToUppercase(one.m_filename); + const std::string two_upper = ASCIIToUppercase(two.m_filename); + return one_upper == two_upper ? one.m_filename < two.m_filename : one_upper < two_upper; }); - for (const File::FSTEntry& entry : sorted_entries) + for (FSTBuilderNode& entry : sorted_entries) { - if (entry.isDirectory) + if (entry.IsFolder()) { u32 entry_index = *fst_offset / ENTRY_SIZE; WriteEntryData(fst_offset, DIRECTORY_ENTRY, *name_offset, parent_entry_index, - entry_index + entry.size + 1, 0); - WriteEntryName(name_offset, entry.virtualName, name_table_offset); + entry_index + entry.m_size + 1, 0); + WriteEntryName(name_offset, entry.m_filename, name_table_offset); - WriteDirectory(entry, fst_offset, name_offset, data_offset, entry_index, name_table_offset); + auto& child_nodes = entry.GetFolderContent(); + WriteDirectory(&child_nodes, fst_offset, name_offset, data_offset, entry_index, + name_table_offset); } else { // put entry in FST - WriteEntryData(fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.size, + WriteEntryData(fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.m_size, m_address_shift); - WriteEntryName(name_offset, entry.virtualName, name_table_offset); + WriteEntryName(name_offset, entry.m_filename, name_table_offset); // write entry to virtual disc - m_contents.Add(*data_offset, entry.size, entry.physicalName); + auto& contents = entry.GetFileContent(); + for (BuilderContentSource& content : contents) + { + m_contents.Add(*data_offset + content.m_offset, content.m_size, + std::move(content.m_source)); + } // 32 KiB aligned - many games are fine with less alignment, but not all - *data_offset = Common::AlignUp(*data_offset + entry.size, 0x8000ull); + *data_offset = Common::AlignUp(*data_offset + entry.m_size, 0x8000ull); } } } @@ -847,30 +1200,6 @@ static void Write32(u32 data, u32 offset, std::vector<u8>* buffer) (*buffer)[offset] = data & 0xff; } -static u32 ComputeNameSize(const File::FSTEntry& parent_entry) -{ - u32 name_size = 0; - for (const File::FSTEntry& entry : parent_entry.children) - { - if (entry.isDirectory) - name_size += ComputeNameSize(entry); - - name_size += (u32)entry.virtualName.length() + 1; - } - return name_size; -} - -static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry* parent_entry) -{ - for (File::FSTEntry& entry : parent_entry->children) - { - if (entry.isDirectory) - ConvertUTF8NamesToSHIFTJIS(&entry); - - entry.virtualName = UTF8ToSHIFTJIS(entry.virtualName); - } -} - static std::string ASCIIToUppercase(std::string str) { std::transform(str.begin(), str.end(), str.begin(), |
