DirectoryBlob: content chunks in one sorted array

A disc's content chunks sit in one malloc'd array sorted by end offset
and found by binary search, in place of a std::set walked node by node.
A chunk is plain data - offset, size, kind, source offset - read through
a switch rather than a std::variant visited per read.

Where a chunk's bytes come from is a small C struct; the bytes it needs
(a host file's path, a run of memory) live in one reference-counted
allocation shared by every chunk and copy that refers to them, in place
of a std::string per chunk and a shared_ptr to a std::vector. Splitting
a memory chunk for a Riivolution patch moves its offset into the shared
bytes instead of copying the tail into a new vector.

The reference counts and the open-file slots' flags are retro_atomic.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-07 04:50:26 +01:00
1 parent 09cac91415
commit a9e048fee2
3 files changed
+436 -215

No files matched your search

+293 -120
View File
@@ -56,136 +56,312 @@ constexpr u8 ENTRY_SIZE = 0x0c;
constexpr u8 FILE_ENTRY = 0;
constexpr u8 DIRECTORY_ENTRY = 1;
DiscContent::DiscContent(u64 offset, u64 size, ContentSource source)
: m_offset(offset), m_size(size), m_content_source(std::move(source))
ContentBlob* ContentBlobAlloc(size_t size)
{
ContentBlob* blob = static_cast<ContentBlob*>(std::malloc(CONTENT_BLOB_HEADER + size));
if (!blob)
return nullptr;
retro_atomic_int_init(&blob->refs, 1);
blob->size = size;
return blob;
}
ContentBlob* ContentBlobCreate(const void* data, size_t size)
{
ContentBlob* blob = ContentBlobAlloc(size);
if (blob && size)
std::memcpy(ContentBlobData(blob), data, size);
return blob;
}
void ContentBlobRetain(ContentBlob* blob)
{
retro_atomic_inc_int(&blob->refs);
}
void ContentBlobRelease(ContentBlob* blob)
{
if (retro_atomic_fetch_sub_int(&blob->refs, 1) == 1)
std::free(blob);
}
ContentSource ContentFile(const char* path, u64 offset)
{
ContentSource source;
source.type = CONTENT_FILE;
source.offset = offset;
source.blob = ContentBlobCreate(path, std::strlen(path) + 1);
return source;
}
ContentSource ContentMemory(ContentBlob* blob, u64 offset)
{
ContentSource source;
source.type = CONTENT_MEMORY;
source.offset = offset;
source.blob = blob;
return source;
}
ContentSource ContentBytes(const void* data, size_t size)
{
return ContentMemory(ContentBlobCreate(data, size), 0);
}
ContentSource ContentPartition(u64 offset, u64 partition_data_offset)
{
ContentSource source;
source.type = CONTENT_PARTITION;
source.offset = offset;
source.partition_data_offset = partition_data_offset;
return source;
}
ContentSource ContentVolume(u64 offset, const Partition& partition)
{
ContentSource source;
source.type = CONTENT_VOLUME;
source.offset = offset;
source.partition = partition;
return source;
}
ContentSource ContentFixedByte(u8 byte)
{
ContentSource source;
source.type = CONTENT_FIXED_BYTE;
source.fixed_byte = byte;
return source;
}
DiscContentContainer::DiscContentContainer() : m_items(nullptr), m_count(0), m_capacity(0)
{
}
DiscContent::DiscContent(u64 offset) : m_offset(offset)
DiscContentContainer::~DiscContentContainer()
{
Clear();
}
u64 DiscContent::GetOffset() const
void DiscContentContainer::Clear()
{
return m_offset;
}
u64 DiscContent::GetEndOffset() const
{
return m_offset + m_size;
}
u64 DiscContent::GetSize() const
{
return m_size;
}
bool DiscContent::Read(u64* offset, u64* length, u8** buffer, DirectoryBlobReader* blob) const
{
if (m_size == 0)
return true;
DEBUG_ASSERT(*offset >= m_offset);
const u64 offset_in_content = *offset - m_offset;
if (offset_in_content < m_size)
size_t i;
for (i = 0; i < m_count; i++)
{
const u64 bytes_to_read = std::min(m_size - offset_in_content, *length);
if (std::holds_alternative<ContentFile>(m_content_source))
{
const auto& content = std::get<ContentFile>(m_content_source);
if (!blob->ReadContentFile(content.m_filename, content.m_offset + offset_in_content, *buffer,
bytes_to_read))
{
return false;
}
}
else if (std::holds_alternative<ContentMemory>(m_content_source))
{
const auto& content = std::get<ContentMemory>(m_content_source);
std::copy_n(content->begin() + offset_in_content, bytes_to_read, *buffer);
}
else if (std::holds_alternative<ContentPartition>(m_content_source))
{
const auto& content = std::get<ContentPartition>(m_content_source);
const u64 decrypted_size = m_size * VolumeWii::BLOCK_DATA_SIZE / VolumeWii::BLOCK_TOTAL_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 (!blob->GetWrappedVolume()->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;
*offset += bytes_to_read;
if (m_items[i].blob)
ContentBlobRelease(m_items[i].blob);
}
return true;
std::free(m_items);
m_items = nullptr;
m_count = 0;
m_capacity = 0;
}
void DiscContentContainer::Add(u64 offset, u64 size, ContentSource source)
DiscContentContainer::DiscContentContainer(const DiscContentContainer& other)
: m_items(nullptr), m_count(0), m_capacity(0)
{
if (size != 0)
m_contents.emplace(offset, size, std::move(source));
*this = other;
}
DiscContentContainer& DiscContentContainer::operator=(const DiscContentContainer& other)
{
size_t i;
DiscContent* items;
if (this == &other)
return *this;
items = other.m_count ?
static_cast<DiscContent*>(std::malloc(other.m_count * sizeof(DiscContent))) :
nullptr;
if (other.m_count && !items)
return *this;
for (i = 0; i < other.m_count; i++)
{
items[i] = other.m_items[i];
if (items[i].blob)
ContentBlobRetain(items[i].blob);
}
Clear();
m_items = items;
m_count = other.m_count;
m_capacity = other.m_count;
return *this;
}
DiscContentContainer::DiscContentContainer(DiscContentContainer&& other) noexcept
: m_items(other.m_items), m_count(other.m_count), m_capacity(other.m_capacity)
{
other.m_items = nullptr;
other.m_count = 0;
other.m_capacity = 0;
}
DiscContentContainer& DiscContentContainer::operator=(DiscContentContainer&& other) noexcept
{
if (this != &other)
{
Clear();
m_items = other.m_items;
m_count = other.m_count;
m_capacity = other.m_capacity;
other.m_items = nullptr;
other.m_count = 0;
other.m_capacity = 0;
}
return *this;
}
// the first chunk ending past offset, m_count if none does
static size_t FirstEndingAfter(const DiscContent* items, size_t count, u64 offset)
{
size_t lo = 0, hi = count;
while (lo < hi)
{
const size_t mid = lo + (hi - lo) / 2;
if (items[mid].offset + items[mid].size > offset)
hi = mid;
else
lo = mid + 1;
}
return lo;
}
void DiscContentContainer::Add(u64 offset, u64 size, const ContentSource& source)
{
size_t at;
DiscContent* item;
if (size == 0)
return;
// chunks are keyed by where they end; a second chunk ending at the same
// place is not added
at = FirstEndingAfter(m_items, m_count, offset + size - 1);
if (at < m_count && m_items[at].offset + m_items[at].size == offset + size)
return;
if (m_count == m_capacity)
{
const size_t capacity = m_capacity ? m_capacity * 2 : 16;
DiscContent* items =
static_cast<DiscContent*>(std::realloc(m_items, capacity * sizeof(DiscContent)));
if (!items)
return;
m_items = items;
m_capacity = capacity;
}
std::memmove(m_items + at + 1, m_items + at, (m_count - at) * sizeof(DiscContent));
m_count++;
item = &m_items[at];
item->offset = offset;
item->size = size;
item->src_offset = source.offset;
item->partition_data_offset = source.partition_data_offset;
item->partition = source.partition;
item->blob = source.blob;
item->type = source.type;
item->fixed_byte = source.fixed_byte;
if (item->blob)
ContentBlobRetain(item->blob);
}
void DiscContentContainer::AddBytes(u64 offset, const void* data, size_t size)
{
if (size)
Add(offset, size, ContentBytes(data, size));
}
u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, const std::string& path)
{
const u64 size = File::GetSize(path);
Add(offset, size, ContentFile{path, 0});
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, ContentFile{path, 0});
Add(offset, size, ContentFile(path, 0));
return size;
}
bool DiscContentContainer::ReadContent(const DiscContent* content, u64* offset, u64* length,
u8** buffer, DirectoryBlobReader* blob) const
{
const u64 offset_in_content = *offset - content->offset;
u64 bytes_to_read;
if (offset_in_content >= content->size)
return true;
bytes_to_read = std::min(content->size - offset_in_content, *length);
switch (content->type)
{
case CONTENT_FILE:
if (!blob->ReadContentFile(reinterpret_cast<const char*>(ContentBlobData(content->blob)),
size_t(content->blob->size - 1),
content->src_offset + offset_in_content, *buffer, bytes_to_read))
{
return false;
}
break;
case CONTENT_MEMORY:
std::memcpy(*buffer, ContentBlobData(content->blob) + content->src_offset + offset_in_content,
size_t(bytes_to_read));
break;
case CONTENT_PARTITION:
{
const u64 decrypted_size =
content->size * VolumeWii::BLOCK_DATA_SIZE / VolumeWii::BLOCK_TOTAL_SIZE;
if (!blob->EncryptPartitionData(content->src_offset + offset_in_content, bytes_to_read,
*buffer, content->partition_data_offset, decrypted_size))
{
return false;
}
break;
}
case CONTENT_VOLUME:
if (!blob->GetWrappedVolume()->Read(content->src_offset + offset_in_content, bytes_to_read,
*buffer, content->partition))
{
return false;
}
break;
case CONTENT_FIXED_BYTE:
std::memset(*buffer, content->fixed_byte, size_t(bytes_to_read));
break;
default:
PanicAlertFmt("DirectoryBlob: Invalid content source in DiscContent.");
return false;
}
*length -= bytes_to_read;
*buffer += bytes_to_read;
*offset += bytes_to_read;
return true;
}
bool DiscContentContainer::Read(u64 offset, u64 length, u8* buffer, DirectoryBlobReader* blob) const
{
// Determine which DiscContent the offset refers to
std::set<DiscContent>::const_iterator it = m_contents.upper_bound(DiscContent(offset));
size_t i = FirstEndingAfter(m_items, m_count, offset);
while (it != m_contents.end() && length > 0)
while (i < m_count && length > 0)
{
const DiscContent* content = &m_items[i];
// Zero fill to start of DiscContent data
PadToAddress(it->GetOffset(), &offset, &length, &buffer);
PadToAddress(content->offset, &offset, &length, &buffer);
if (length == 0)
return true;
if (!it->Read(&offset, &length, &buffer, blob))
if (!ReadContent(content, &offset, &length, &buffer, blob))
return false;
++it;
DEBUG_ASSERT(it == m_contents.end() || it->GetOffset() >= offset);
i++;
}
// Zero fill if we went beyond the last DiscContent
std::fill_n(buffer, static_cast<size_t>(length), 0);
std::memset(buffer, 0, static_cast<size_t>(length));
return true;
}
@@ -511,7 +687,7 @@ void DirectoryBlobReader::InitFileSlots()
u32 i;
for (i = 0; i < NUM_FILE_SLOTS; i++)
{
m_file_slots[i].busy.store(0, std::memory_order_relaxed);
retro_atomic_int_init(&m_file_slots[i].busy, 0);
m_file_slots[i].file = nullptr;
m_file_slots[i].map = nullptr;
m_file_slots[i].map_size = 0;
@@ -529,10 +705,9 @@ DirectoryBlobReader::~DirectoryBlobReader()
}
}
bool DirectoryBlobReader::ReadContentFile(const std::string& path, u64 offset, u8* buffer, u64 size)
bool DirectoryBlobReader::ReadContentFile(const char* name, size_t name_len, u64 offset,
u8* buffer, u64 size)
{
const char* name = path.c_str();
const size_t name_len = path.size();
FileSlot* slot;
bool ok;
u32 hash = 2166136261u;
@@ -542,7 +717,7 @@ bool DirectoryBlobReader::ReadContentFile(const std::string& path, u64 offset, u
hash = (hash ^ u8(name[i])) * 16777619u;
slot = &m_file_slots[hash % NUM_FILE_SLOTS];
if (name_len >= sizeof(slot->path) || slot->busy.exchange(1, std::memory_order_acquire))
if (name_len >= sizeof(slot->path) || retro_atomic_exchange_int(&slot->busy, 1))
{
File::DirectIOFile file(name, File::AccessMode::Read);
return file.OffsetRead(offset, buffer, size);
@@ -572,7 +747,7 @@ bool DirectoryBlobReader::ReadContentFile(const std::string& path, u64 offset, u
filestream_read(slot->file, buffer, s64(size)) == s64(size);
}
slot->busy.store(0, std::memory_order_release);
retro_atomic_store_release_int(&slot->busy, 0);
return ok;
}
@@ -682,7 +857,7 @@ void DirectoryBlobReader::SetNonpartitionDiscHeader(std::span<const u8> partitio
m_encrypted =
std::all_of(header_bin.data() + 0x60, header_bin.data() + 0x64, [](u8 x) { return x == 0; });
m_nonpartition_contents.Add(WII_NONPARTITION_DISCHEADER_ADDRESS, std::move(header_bin));
m_nonpartition_contents.AddBytes(WII_NONPARTITION_DISCHEADER_ADDRESS, header_bin.data(), header_bin.size());
}
void DirectoryBlobReader::SetWiiRegionDataFromFile(const std::string& game_partition_root)
@@ -709,7 +884,7 @@ void DirectoryBlobReader::SetWiiRegionData(std::span<const u8> wii_region_data,
else if (wii_region_data.size() < 0x20)
ERROR_LOG_FMT(DISCIO, "Couldn't read age ratings from {}", log_path);
m_nonpartition_contents.Add(WII_REGION_DATA_ADDRESS, std::move(region_data));
m_nonpartition_contents.AddBytes(WII_REGION_DATA_ADDRESS, region_data.data(), region_data.size());
}
void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partitions)
@@ -772,14 +947,14 @@ void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partiti
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, encrypted_data_size,
ContentPartition{0, partition_data_offset});
ContentPartition(0, partition_data_offset));
const u64 unaligned_next_partition_address = VolumeWii::OffsetInHashedPartitionToRawOffset(
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, std::move(partition_table));
m_nonpartition_contents.AddBytes(PARTITION_TABLE_ADDRESS, partition_table.data(), partition_table.size());
}
// This function sets the header that's shortly before the start of the encrypted
@@ -800,8 +975,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
if (new_ticket.size() > WII_PARTITION_TICKET_SIZE)
new_ticket.resize(WII_PARTITION_TICKET_SIZE);
ticket_size = new_ticket.size();
m_nonpartition_contents.Add(partition_address + WII_PARTITION_TICKET_ADDRESS,
std::move(new_ticket));
m_nonpartition_contents.AddBytes(partition_address + WII_PARTITION_TICKET_ADDRESS, new_ticket.data(), new_ticket.size());
}
else
{
@@ -817,7 +991,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
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.Add(partition_address + TMD_OFFSET, std::move(new_tmd));
m_nonpartition_contents.AddBytes(partition_address + TMD_OFFSET, new_tmd.data(), new_tmd.size());
}
else
{
@@ -835,7 +1009,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
if (new_cert.size() > max_cert_size)
new_cert.resize(max_cert_size);
cert_size = new_cert.size();
m_nonpartition_contents.Add(partition_address + cert_offset, std::move(new_cert));
m_nonpartition_contents.AddBytes(partition_address + cert_offset, new_cert.data(), new_cert.size());
}
else
{
@@ -855,7 +1029,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
if (m_wrapped_volume->Read(wrapped_partition->offset + *offset, new_h3.size(),
new_h3.data(), PARTITION_NONE))
{
m_nonpartition_contents.Add(partition_address + H3_OFFSET, std::move(new_h3));
m_nonpartition_contents.AddBytes(partition_address + H3_OFFSET, new_h3.data(), new_h3.size());
}
}
}
@@ -877,8 +1051,7 @@ 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,
std::move(partition_header));
m_nonpartition_contents.AddBytes(partition_address + WII_PARTITION_TICKET_SIZE, partition_header.data(), partition_header.size());
std::vector<u8> ticket_buffer(ticket_size);
m_nonpartition_contents.Read(partition_address + WII_PARTITION_TICKET_ADDRESS, ticket_size,
@@ -905,7 +1078,7 @@ static void GenerateBuilderNodesFromFileSystem(const VolumeDisc& volume, const P
{
std::vector<BuilderContentSource> source;
source.emplace_back(BuilderContentSource{0, file_info.GetSize(),
ContentVolume{file_info.GetOffset(), partition}});
ContentVolume(file_info.GetOffset(), partition)});
nodes->emplace_back(
FSTBuilderNode{file_info.GetName(), file_info.GetSize(), std::move(source)});
}
@@ -927,7 +1100,7 @@ DirectoryBlobPartition::DirectoryBlobPartition(std::string root_directory,
SetDOLFromFile(m_root_directory + "sys/main.dol", dol_address, &disc_header);
BuildFSTFromFolder(m_root_directory + "files/", fst_address, &disc_header);
m_contents.Add(DISCHEADER_ADDRESS, disc_header);
m_contents.AddBytes(DISCHEADER_ADDRESS, disc_header.data(), disc_header.size());
}
static void FillSingleFileNode(FSTBuilderNode* node, std::vector<u8> data)
@@ -935,7 +1108,7 @@ static void FillSingleFileNode(FSTBuilderNode* node, std::vector<u8> data)
std::vector<BuilderContentSource> contents;
const size_t size = data.size();
contents.emplace_back(
BuilderContentSource{0, size, std::make_shared<std::vector<u8>>(std::move(data))});
BuilderContentSource{0, size, ContentBytes(data.data(), data.size())});
node->m_size = size;
node->m_content = std::move(contents);
}
@@ -958,7 +1131,7 @@ static std::vector<u8> ExtractNodeToVector(std::vector<FSTBuilderNode>* nodes, v
DiscContentContainer tmp;
for (auto& content : it->GetFileContent())
tmp.Add(content.m_offset, content.m_size, std::move(content.m_source));
tmp.Add(content.m_offset, content.m_size, content.m_source);
data.resize(it->m_size);
tmp.Read(0, it->m_size, data.data(), blob);
return data;
@@ -1015,7 +1188,7 @@ DirectoryBlobPartition::DirectoryBlobPartition(
{
std::vector<BuilderContentSource> dol_contents;
dol_contents.emplace_back(
BuilderContentSource{0, *dol_size, ContentVolume{*dol_offset, partition}});
BuilderContentSource{0, *dol_size, ContentVolume(*dol_offset, partition)});
dol_node.m_size = *dol_size;
dol_node.m_content = std::move(dol_contents);
}
@@ -1033,7 +1206,7 @@ DirectoryBlobPartition::DirectoryBlobPartition(
const u64 new_fst_address = SetDOL(std::move(dol_node), new_dol_address, &disc_header);
BuildFST(std::move(nodes), new_fst_address, &disc_header);
m_contents.Add(DISCHEADER_ADDRESS, disc_header);
m_contents.AddBytes(DISCHEADER_ADDRESS, disc_header.data(), disc_header.size());
}
void DirectoryBlobPartition::SetDiscType(std::optional<bool> is_wii,
@@ -1068,7 +1241,7 @@ void DirectoryBlobPartition::SetBI2FromFile(const std::string& bi2_path)
if (!m_is_wii && bytes_read < 0x1C)
ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", bi2_path);
m_contents.Add(BI2_ADDRESS, std::move(bi2));
m_contents.AddBytes(BI2_ADDRESS, bi2.data(), bi2.size());
}
void DirectoryBlobPartition::SetBI2(std::vector<u8> bi2)
@@ -1079,7 +1252,7 @@ void DirectoryBlobPartition::SetBI2(std::vector<u8> bi2)
if (!m_is_wii && bi2_size < 0x1C)
Write32(INVALID_REGION, 0x18, &bi2);
m_contents.Add(BI2_ADDRESS, std::move(bi2));
m_contents.AddBytes(BI2_ADDRESS, bi2.data(), bi2.size());
}
u64 DirectoryBlobPartition::SetApploaderFromFile(const std::string& path)
@@ -1116,7 +1289,7 @@ u64 DirectoryBlobPartition::SetApploader(std::vector<u8> apploader, const std::s
}
size_t apploader_size = apploader.size();
m_contents.Add(APPLOADER_ADDRESS, std::move(apploader));
m_contents.AddBytes(APPLOADER_ADDRESS, apploader.data(), apploader.size());
// Return DOL address, 32 byte aligned (plus 32 byte padding)
return Common::AlignUp(APPLOADER_ADDRESS + apploader_size + 0x20, 0x20ull);
@@ -1137,7 +1310,7 @@ u64 DirectoryBlobPartition::SetDOL(FSTBuilderNode dol_node, u64 dol_address,
std::vector<u8>* disc_header)
{
for (auto& content : dol_node.GetFileContent())
m_contents.Add(dol_address + content.m_offset, content.m_size, std::move(content.m_source));
m_contents.Add(dol_address + content.m_offset, content.m_size, content.m_source);
Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, disc_header);
@@ -1159,7 +1332,7 @@ static std::vector<FSTBuilderNode> ConvertFSTEntriesToBuilderNodes(const File::F
else
{
content =
std::vector<BuilderContentSource>{{0, entry.size, ContentFile{entry.physicalName, 0}}};
std::vector<BuilderContentSource>{{0, entry.size, ContentFile(entry.physicalName, 0)}};
}
nodes.emplace_back(FSTBuilderNode{entry.virtualName, entry.size, std::move(content)});
@@ -1245,7 +1418,7 @@ void DirectoryBlobPartition::BuildFST(std::vector<FSTBuilderNode> root_nodes, u6
Write32((u32)(fst_data.size() >> m_address_shift), 0x0428, disc_header);
Write32((u32)(fst_data.size() >> m_address_shift), 0x042c, disc_header);
m_contents.Add(fst_address, std::move(fst_data));
m_contents.AddBytes(fst_address, fst_data.data(), fst_data.size());
m_data_size = current_data_address;
}
@@ -1324,7 +1497,7 @@ void DirectoryBlobPartition::WriteDirectory(std::vector<u8>* fst_data,
for (BuilderContentSource& content : contents)
{
m_contents.Add(*data_offset + content.m_offset, content.m_size,
std::move(content.m_source));
content.m_source);
}
// For most files, an alignment of 0x20 is enough. But some files need 0x8000,
+136 -73
View File
@@ -4,17 +4,16 @@
#pragma once
#include <array>
#include <atomic>
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <variant>
#include <vector>
#include <retro_atomic.h>
#include <retro_miscellaneous.h>
#include "Common/CommonTypes.h"
@@ -39,53 +38,123 @@ class VolumeDisc;
// Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself
bool ShouldHideFromGameList(const std::string& volume_path);
// Content chunk that is loaded from a file in the host file system.
struct ContentFile
// Bytes shared by the content chunks that refer to them: a host file's path
// (NUL included) or a run of memory. Reference counted; the bytes follow the
// header in the same allocation.
struct ContentBlob
{
// Path where the file can be found.
std::string m_filename;
// Offset from the start of the file where the first byte of this content chunk is.
u64 m_offset = 0;
retro_atomic_int_t refs;
u64 size;
};
// Content chunk that's just a direct block of memory
typedef std::shared_ptr<std::vector<u8>> ContentMemory;
// the bytes start here, 16-aligned whatever the atomic's width
#define CONTENT_BLOB_HEADER ((sizeof(ContentBlob) + 15) & ~size_t(15))
// Content chunk that loads data from a DirectoryBlobReader.
// Intended for representing a partition within a disc.
struct ContentPartition
// The size octets after the header, uninitialized; one reference.
ContentBlob* ContentBlobAlloc(size_t size);
ContentBlob* ContentBlobCreate(const void* data, size_t size);
void ContentBlobRetain(ContentBlob* blob);
void ContentBlobRelease(ContentBlob* blob);
inline u8* ContentBlobData(ContentBlob* blob)
{
// Offset from the start of the partition for the first byte represented by this chunk.
u64 m_offset = 0;
return reinterpret_cast<u8*>(blob) + CONTENT_BLOB_HEADER;
}
inline const u8* ContentBlobData(const ContentBlob* blob)
{
return reinterpret_cast<const u8*>(blob) + CONTENT_BLOB_HEADER;
}
// The value passed as partition_data_offset to EncryptPartitionData().
u64 m_partition_data_offset = 0;
enum ContentType : u8
{
// a host file: blob is its path, offset the file offset of the first byte
CONTENT_FILE,
// blob's bytes from offset on
CONTENT_MEMORY,
// the reader's own partition, encrypted on the fly: offset into it, and
// the partition_data_offset passed to EncryptPartitionData()
CONTENT_PARTITION,
// the wrapped volume: offset into it, read from partition
CONTENT_VOLUME,
// fixed_byte repeated
CONTENT_FIXED_BYTE,
};
// Content chunk that loads data from a Volume.
struct ContentVolume
// Where a content chunk's bytes come from. Copies share the blob.
struct ContentSource
{
// Offset from the start of the volume for the first byte represented by this chunk.
u64 m_offset = 0;
ContentSource()
: type(CONTENT_FIXED_BYTE), fixed_byte(0), offset(0), partition_data_offset(0),
blob(nullptr)
{
}
ContentSource(const ContentSource& other)
: type(other.type), fixed_byte(other.fixed_byte), offset(other.offset),
partition_data_offset(other.partition_data_offset), partition(other.partition),
blob(other.blob)
{
if (blob)
ContentBlobRetain(blob);
}
ContentSource(ContentSource&& other) noexcept
: type(other.type), fixed_byte(other.fixed_byte), offset(other.offset),
partition_data_offset(other.partition_data_offset), partition(other.partition),
blob(other.blob)
{
other.blob = nullptr;
}
ContentSource& operator=(const ContentSource& other)
{
if (other.blob)
ContentBlobRetain(other.blob);
if (blob)
ContentBlobRelease(blob);
type = other.type;
fixed_byte = other.fixed_byte;
offset = other.offset;
partition_data_offset = other.partition_data_offset;
partition = other.partition;
blob = other.blob;
return *this;
}
ContentSource& operator=(ContentSource&& other) noexcept
{
ContentBlob* const old = blob;
type = other.type;
fixed_byte = other.fixed_byte;
offset = other.offset;
partition_data_offset = other.partition_data_offset;
partition = other.partition;
blob = other.blob;
other.blob = nullptr;
if (old)
ContentBlobRelease(old);
return *this;
}
~ContentSource()
{
if (blob)
ContentBlobRelease(blob);
}
// The partition passed to the Volume's Read() method.
Partition m_partition;
u8 type;
u8 fixed_byte;
u64 offset;
u64 partition_data_offset;
Partition partition;
ContentBlob* blob;
};
// Content chunk representing a run of identical bytes.
// Useful for padding between chunks within a file.
struct ContentFixedByte
ContentSource ContentFile(const char* path, u64 offset);
inline ContentSource ContentFile(const std::string& path, u64 offset)
{
u8 m_byte = 0;
};
using ContentSource = std::variant<ContentFile, // File
ContentMemory, // Memory/Byte Sequence
ContentPartition, // Partition
ContentVolume, // Volume
ContentFixedByte // Fixed value padding
>;
return ContentFile(path.c_str(), offset);
}
// Takes over the caller's reference.
ContentSource ContentMemory(ContentBlob* blob, u64 offset);
ContentSource ContentBytes(const void* data, size_t size);
ContentSource ContentPartition(u64 offset, u64 partition_data_offset);
ContentSource ContentVolume(u64 offset, const Partition& partition);
ContentSource ContentFixedByte(u8 byte);
struct BuilderContentSource
{
@@ -129,52 +198,46 @@ struct FSTBuilderNode
}
};
class DiscContent
// A content chunk placed at offset on the disc. Plain data: the container
// it sits in holds the blob's reference.
struct DiscContent
{
public:
DiscContent(u64 offset, u64 size, ContentSource source);
// Provided because it's convenient when searching for DiscContent in an std::set
explicit DiscContent(u64 offset);
u64 GetOffset() const;
u64 GetEndOffset() const;
u64 GetSize() const;
bool Read(u64* offset, u64* length, u8** buffer, DirectoryBlobReader* blob) const;
bool operator==(const DiscContent& other) const { return GetEndOffset() == other.GetEndOffset(); }
bool operator<(const DiscContent& other) const { return GetEndOffset() < other.GetEndOffset(); }
bool operator>(const DiscContent& other) const { return other < *this; }
bool operator<=(const DiscContent& other) const { return !(*this > other); }
bool operator>=(const DiscContent& other) const { return !(*this < other); }
private:
// Position of this content chunk within its parent DiscContentContainer.
u64 m_offset;
// Number of bytes this content chunk takes up.
u64 m_size = 0;
// Where and how to find the data for this content chunk.
ContentSource m_content_source;
u64 offset;
u64 size;
u64 src_offset;
u64 partition_data_offset;
Partition partition;
ContentBlob* blob;
u8 type;
u8 fixed_byte;
};
// Content chunks sorted by end offset in one array, found by binary search.
class DiscContentContainer
{
public:
void Add(u64 offset, std::vector<u8> vector)
{
size_t vector_size = vector.size();
return Add(offset, vector_size, std::make_shared<std::vector<u8>>(std::move(vector)));
}
void Add(u64 offset, u64 size, ContentSource source);
DiscContentContainer();
~DiscContentContainer();
DiscContentContainer(const DiscContentContainer& other);
DiscContentContainer& operator=(const DiscContentContainer& other);
DiscContentContainer(DiscContentContainer&& other) noexcept;
DiscContentContainer& operator=(DiscContentContainer&& other) noexcept;
void Add(u64 offset, u64 size, const ContentSource& source);
void AddBytes(u64 offset, const void* data, size_t size);
u64 CheckSizeAndAdd(u64 offset, const std::string& path);
u64 CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path);
bool Read(u64 offset, u64 length, u8* buffer, DirectoryBlobReader* blob) const;
private:
std::set<DiscContent> m_contents;
void Clear();
bool ReadContent(const DiscContent* content, u64* offset, u64* length, u8** buffer,
DirectoryBlobReader* blob) const;
DiscContent* m_items;
size_t m_count;
size_t m_capacity;
};
class DirectoryBlobPartition
@@ -247,7 +310,7 @@ private:
class DirectoryBlobReader final : public BlobReader
{
friend DiscContent;
friend DiscContentContainer;
public:
static std::unique_ptr<DirectoryBlobReader> Create(const std::string& dol_path);
@@ -314,7 +377,7 @@ private:
const VolumeDisc* GetWrappedVolume() const { return m_wrapped_volume.get(); }
// Reads from a host file, through a handle kept open across reads.
bool ReadContentFile(const std::string& path, u64 offset, u8* buffer, u64 size);
bool ReadContentFile(const char* path, size_t path_len, u64 offset, u8* buffer, u64 size);
// Recently read host files, kept open (and mapped where the platform maps)
// so a read is a copy out of the page cache rather than an open, a map, an
@@ -323,7 +386,7 @@ private:
static constexpr u32 NUM_FILE_SLOTS = 16;
struct FileSlot
{
std::atomic<u32> busy;
retro_atomic_int_t busy;
RFILE* file;
const u8* map;
u64 map_size;
+7 -22
View File
@@ -208,9 +208,9 @@ FileDataLoaderHostFS::MakeContentSource(std::string_view external_relative_path,
{
auto path = MakeAbsoluteFromRelative(external_relative_path);
if (!path)
return BuilderContentSource{disc_offset, external_size, ContentFixedByte{}};
return BuilderContentSource{disc_offset, external_size, ContentFixedByte(0)};
return BuilderContentSource{disc_offset, external_size,
ContentFile{std::move(*path), external_offset}};
ContentFile(*path, external_offset)};
}
std::optional<std::string>
@@ -233,24 +233,9 @@ static void SplitAt(BuilderContentSource* before, BuilderContentSource* after, u
// The source after the split needs its length reduced and its start point adjusted.
after->m_offset += before->m_size;
after->m_size = end - split_at;
if (std::holds_alternative<ContentFile>(after->m_source))
{
std::get<ContentFile>(after->m_source).m_offset += before->m_size;
}
else if (std::holds_alternative<ContentMemory>(after->m_source))
{
after->m_source = std::make_shared<std::vector<u8>>(
std::get<ContentMemory>(after->m_source)->begin() + before->m_size,
std::get<ContentMemory>(after->m_source)->end());
}
else if (std::holds_alternative<ContentPartition>(after->m_source))
{
std::get<ContentPartition>(after->m_source).m_offset += before->m_size;
}
else if (std::holds_alternative<ContentVolume>(after->m_source))
{
std::get<ContentVolume>(after->m_source).m_offset += before->m_size;
}
// every kind but fixed bytes moves its start; memory keeps sharing the blob
if (after->m_source.type != CONTENT_FIXED_BYTE)
after->m_source.offset += before->m_size;
}
static void ApplyPatchToFile(const Patch& patch, FSTBuilderNode* file_node,
@@ -282,7 +267,7 @@ static void ApplyPatchToFile(const Patch& patch, FSTBuilderNode* file_node,
{
// Insert an padding area between the old file and the patch data.
content.emplace_back(BuilderContentSource{file_node->m_size, patch_start - file_node->m_size,
ContentFixedByte{}});
ContentFixedByte(0)});
}
insert_where = content.size();
@@ -338,7 +323,7 @@ static void ApplyPatchToFile(const Patch& patch, FSTBuilderNode* file_node,
if (external_filesize < patch_size)
{
BuilderContentSource padding{patch_start + external_filesize, patch_size - external_filesize,
ContentFixedByte{}};
ContentFixedByte(0)};
content.emplace(content.begin() + insert_where, std::move(padding));
}