mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-07 04:26:01 -10:00
Names, game IDs, patch lists, LBA ranges and hash exceptions are passed as pointer plus count. min/max/fill/copy become ternaries, memset and memcpy; FST entries sort with an in-place heap sort, partitions with an insertion sort. WAD content checks decrypt and hash in 2 KiB chunks straight from the borrowed input; banner names read onto the stack.
1700 lines
55 KiB
C++
1700 lines
55 KiB
C++
// Copyright 2008 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DiscIO/DirectoryBlob.h"
|
|
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
#include <streams/file_stream.h>
|
|
|
|
#include "Common/Align.h"
|
|
#include "Common/Assert.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/DirectIOFile.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/Logging/Log.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Common/Swap.h"
|
|
#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"
|
|
|
|
namespace DiscIO
|
|
{
|
|
// Reads as many bytes as the vector fits (or less, if the file is smaller).
|
|
// Returns the number of bytes read.
|
|
static size_t ReadFileToBuffer(const std::string& path, u8* buffer, size_t size);
|
|
|
|
static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer);
|
|
static void Write32(u32 data, u32 offset, u8* buffer);
|
|
|
|
enum class PartitionType : u32
|
|
{
|
|
Game = 0,
|
|
Update = 1,
|
|
Channel = 2,
|
|
// There are more types used by Super Smash Bros. Brawl, but they don't have special names
|
|
};
|
|
|
|
// 0xFF is an arbitrarily picked value. Note that we can't use 0x00, because that means NTSC-J
|
|
constexpr u32 INVALID_REGION = 0xFF;
|
|
|
|
constexpr u32 PARTITION_DATA_OFFSET = 0x20000;
|
|
|
|
constexpr u8 ENTRY_SIZE = 0x0c;
|
|
constexpr u8 FILE_ENTRY = 0;
|
|
constexpr u8 DIRECTORY_ENTRY = 1;
|
|
|
|
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;
|
|
}
|
|
|
|
// A zeroed blob of size octets.
|
|
static ContentBlob* ZeroedBlob(size_t size)
|
|
{
|
|
ContentBlob* blob = ContentBlobAlloc(size);
|
|
if (blob)
|
|
std::memset(ContentBlobData(blob), 0, 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)
|
|
{
|
|
}
|
|
|
|
DiscContentContainer::~DiscContentContainer()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void DiscContentContainer::Clear()
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < m_count; i++)
|
|
{
|
|
if (m_items[i].blob)
|
|
ContentBlobRelease(m_items[i].blob);
|
|
}
|
|
std::free(m_items);
|
|
m_items = nullptr;
|
|
m_count = 0;
|
|
m_capacity = 0;
|
|
}
|
|
|
|
DiscContentContainer::DiscContentContainer(const DiscContentContainer& other)
|
|
: m_items(nullptr), m_count(0), m_capacity(0)
|
|
{
|
|
*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));
|
|
return size;
|
|
}
|
|
|
|
u64 DiscContentContainer::CheckSizeAndAdd(u64 offset, u64 max_size, const std::string& path)
|
|
{
|
|
const u64 file_size = File::GetSize(path);
|
|
const u64 size = file_size < max_size ? file_size : max_size;
|
|
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 = content->size - offset_in_content;
|
|
if (*length < bytes_to_read)
|
|
bytes_to_read = *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
|
|
{
|
|
size_t i = FirstEndingAfter(m_items, m_count, offset);
|
|
|
|
while (i < m_count && length > 0)
|
|
{
|
|
const DiscContent* content = &m_items[i];
|
|
|
|
// Zero fill to start of DiscContent data
|
|
PadToAddress(content->offset, &offset, &length, &buffer);
|
|
|
|
if (length == 0)
|
|
return true;
|
|
|
|
if (!ReadContent(content, &offset, &length, &buffer, blob))
|
|
return false;
|
|
|
|
i++;
|
|
}
|
|
|
|
// Zero fill if we went beyond the last DiscContent
|
|
std::memset(buffer, 0, static_cast<size_t>(length));
|
|
|
|
return true;
|
|
}
|
|
|
|
static std::optional<PartitionType> ParsePartitionDirectoryName(const std::string& name)
|
|
{
|
|
if (name.size() < 2)
|
|
return {};
|
|
|
|
if (!strcasecmp(name.c_str(), "DATA"))
|
|
return PartitionType::Game;
|
|
if (!strcasecmp(name.c_str(), "UPDATE"))
|
|
return PartitionType::Update;
|
|
if (!strcasecmp(name.c_str(), "CHANNEL"))
|
|
return PartitionType::Channel;
|
|
|
|
if (name[0] == 'P' || name[0] == 'p')
|
|
{
|
|
// e.g. "P-HA8E" (normally only used for Super Smash Bros. Brawl's VC partitions)
|
|
if (name[1] == '-' && name.size() == 6)
|
|
{
|
|
const u32 result = Common::swap32(reinterpret_cast<const u8*>(name.data() + 2));
|
|
return static_cast<PartitionType>(result);
|
|
}
|
|
|
|
// e.g. "P0"
|
|
size_t digits = 1;
|
|
while (digits < name.size() && name[digits] >= '0' && name[digits] <= '9')
|
|
digits++;
|
|
if (digits == name.size())
|
|
{
|
|
u32 result;
|
|
if (TryParse(name.substr(1), &result))
|
|
return static_cast<PartitionType>(result);
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
static bool IsDirectorySeparator(char c)
|
|
{
|
|
return c == '/'
|
|
#ifdef _WIN32
|
|
|| c == '\\'
|
|
#endif
|
|
;
|
|
}
|
|
|
|
static bool PathCharactersEqual(char a, char b)
|
|
{
|
|
return a == b || (IsDirectorySeparator(a) && IsDirectorySeparator(b));
|
|
}
|
|
|
|
static bool PathEndsWith(const std::string& path, const std::string_view suffix)
|
|
{
|
|
if (suffix.size() > path.size())
|
|
return false;
|
|
|
|
std::string::const_iterator path_iterator = path.cend() - suffix.size();
|
|
std::string_view::const_iterator suffix_iterator = suffix.cbegin();
|
|
while (path_iterator != path.cend())
|
|
{
|
|
if (!PathCharactersEqual(*path_iterator, *suffix_iterator))
|
|
return false;
|
|
path_iterator++;
|
|
suffix_iterator++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IsValidDirectoryBlob(const std::string& dol_path, std::string* partition_root,
|
|
std::string* true_root = nullptr)
|
|
{
|
|
if (!PathEndsWith(dol_path, "/sys/main.dol"))
|
|
return false;
|
|
|
|
static constexpr size_t chars_to_remove = std::string_view("sys/main.dol").size();
|
|
*partition_root = dol_path.substr(0, dol_path.size() - chars_to_remove);
|
|
|
|
if (File::GetSize(*partition_root + "sys/boot.bin") < 0x20)
|
|
return false;
|
|
|
|
#ifdef _WIN32
|
|
constexpr const char* dir_separator = "/\\";
|
|
#else
|
|
constexpr char dir_separator = '/';
|
|
#endif
|
|
if (true_root)
|
|
{
|
|
*true_root =
|
|
dol_path.substr(0, dol_path.find_last_of(dir_separator, partition_root->size() - 2) + 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ExistsAndIsValidDirectoryBlob(const std::string& dol_path)
|
|
{
|
|
std::string partition_root;
|
|
return File::Exists(dol_path) && IsValidDirectoryBlob(dol_path, &partition_root);
|
|
}
|
|
|
|
static bool IsInFilesDirectory(const std::string& path)
|
|
{
|
|
size_t files_pos = std::string::npos;
|
|
while (true)
|
|
{
|
|
files_pos = path.rfind("files", files_pos);
|
|
if (files_pos == std::string::npos)
|
|
return false;
|
|
|
|
const size_t slash_before_pos = files_pos - 1;
|
|
const size_t slash_after_pos = files_pos + 5;
|
|
if ((files_pos == 0 || IsDirectorySeparator(path[slash_before_pos])) &&
|
|
(slash_after_pos == path.size() || (IsDirectorySeparator(path[slash_after_pos]))) &&
|
|
ExistsAndIsValidDirectoryBlob(path.substr(0, files_pos) + "sys/main.dol"))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
--files_pos;
|
|
}
|
|
}
|
|
|
|
static bool IsMainDolForNonGamePartition(const std::string& path)
|
|
{
|
|
std::string partition_root, true_root;
|
|
if (!IsValidDirectoryBlob(path, &partition_root, &true_root))
|
|
return false; // This is not a /sys/main.dol
|
|
|
|
std::string partition_directory_name = partition_root.substr(true_root.size());
|
|
partition_directory_name.pop_back(); // Remove trailing slash
|
|
const std::optional<PartitionType> partition_type =
|
|
ParsePartitionDirectoryName(partition_directory_name);
|
|
if (!partition_type || *partition_type == PartitionType::Game)
|
|
return false; // volume_path is the game partition's /sys/main.dol
|
|
|
|
const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false);
|
|
for (const File::FSTEntry& entry : true_root_entry.children)
|
|
{
|
|
if (entry.isDirectory &&
|
|
ParsePartitionDirectoryName(entry.virtualName) == PartitionType::Game &&
|
|
ExistsAndIsValidDirectoryBlob(entry.physicalName + "/sys/main.dol"))
|
|
{
|
|
return true; // volume_path is the /sys/main.dol for a non-game partition
|
|
}
|
|
}
|
|
|
|
return false; // volume_path is the game partition's /sys/main.dol
|
|
}
|
|
|
|
static bool IsBootBin(const std::string& path)
|
|
{
|
|
static constexpr std::string_view boot_bin = "/sys/boot.bin";
|
|
if (!PathEndsWith(path, boot_bin))
|
|
return false;
|
|
|
|
const std::string partition_root = path.substr(0, path.size() - boot_bin.size());
|
|
return File::Exists(partition_root + "/sys/main.dol");
|
|
}
|
|
|
|
static bool IsHeaderBin(const std::string& path)
|
|
{
|
|
static constexpr std::string_view header_bin = "/disc/header.bin";
|
|
if (!PathEndsWith(path, header_bin))
|
|
return false;
|
|
|
|
const std::string partition_root = path.substr(0, path.size() - header_bin.size());
|
|
return File::Exists(partition_root + "/sys/main.dol");
|
|
}
|
|
|
|
bool ShouldHideFromGameList(const std::string& volume_path)
|
|
{
|
|
return IsInFilesDirectory(volume_path) || IsMainDolForNonGamePartition(volume_path) ||
|
|
IsBootBin(volume_path) || IsHeaderBin(volume_path);
|
|
}
|
|
|
|
std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(const std::string& dol_path)
|
|
{
|
|
std::string partition_root, true_root;
|
|
if (!IsValidDirectoryBlob(dol_path, &partition_root, &true_root))
|
|
return nullptr;
|
|
|
|
return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(partition_root, true_root));
|
|
}
|
|
|
|
std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(
|
|
std::unique_ptr<VolumeDisc> volume,
|
|
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
|
|
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), sys_callback, fst_callback));
|
|
}
|
|
|
|
DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root,
|
|
const std::string& true_root)
|
|
: m_encryption_cache(this)
|
|
{
|
|
InitFileSlots();
|
|
DirectoryBlobPartition game_partition(game_partition_root, {});
|
|
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
|
|
{
|
|
u8 disc_header[DISCHEADER_SIZE];
|
|
game_partition.GetContents().Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header, this);
|
|
SetNonpartitionDiscHeaderFromFile(disc_header, game_partition_root);
|
|
SetWiiRegionDataFromFile(game_partition_root);
|
|
|
|
std::vector<PartitionWithType> partitions;
|
|
partitions.emplace_back(std::move(game_partition), PartitionType::Game);
|
|
|
|
std::string game_partition_directory_name = game_partition_root.substr(true_root.size());
|
|
game_partition_directory_name.pop_back(); // Remove trailing slash
|
|
if (ParsePartitionDirectoryName(game_partition_directory_name) == PartitionType::Game)
|
|
{
|
|
const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false);
|
|
for (const File::FSTEntry& entry : true_root_entry.children)
|
|
{
|
|
if (entry.isDirectory)
|
|
{
|
|
const std::optional<PartitionType> type = ParsePartitionDirectoryName(entry.virtualName);
|
|
if (type && *type != PartitionType::Game)
|
|
{
|
|
partitions.emplace_back(DirectoryBlobPartition(entry.physicalName + "/", m_is_wii),
|
|
*type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SetPartitions(std::move(partitions));
|
|
}
|
|
}
|
|
|
|
DirectoryBlobReader::DirectoryBlobReader(
|
|
std::unique_ptr<VolumeDisc> volume,
|
|
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
|
|
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
|
|
fst_callback)
|
|
: m_encryption_cache(this), m_wrapped_volume(std::move(volume))
|
|
{
|
|
InitFileSlots();
|
|
DirectoryBlobPartition game_partition(m_wrapped_volume.get(),
|
|
m_wrapped_volume->GetGamePartition(), std::nullopt,
|
|
sys_callback, fst_callback, this);
|
|
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
|
|
{
|
|
u8 header_bin[WII_NONPARTITION_DISCHEADER_SIZE];
|
|
const size_t header_bin_size =
|
|
m_wrapped_volume->Read(WII_NONPARTITION_DISCHEADER_ADDRESS,
|
|
WII_NONPARTITION_DISCHEADER_SIZE, header_bin, PARTITION_NONE) ?
|
|
WII_NONPARTITION_DISCHEADER_SIZE :
|
|
0;
|
|
u8 disc_header[DISCHEADER_SIZE];
|
|
game_partition.GetContents().Read(DISCHEADER_ADDRESS, DISCHEADER_SIZE, disc_header, this);
|
|
SetNonpartitionDiscHeader(disc_header, header_bin, header_bin_size);
|
|
|
|
u8 wii_region_data[WII_REGION_DATA_SIZE];
|
|
const size_t wii_region_size =
|
|
m_wrapped_volume->Read(WII_REGION_DATA_ADDRESS, WII_REGION_DATA_SIZE, wii_region_data,
|
|
PARTITION_NONE) ?
|
|
WII_REGION_DATA_SIZE :
|
|
0;
|
|
SetWiiRegionData(wii_region_data, wii_region_size, "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, nullptr, this),
|
|
static_cast<PartitionType>(*type));
|
|
}
|
|
}
|
|
|
|
SetPartitions(std::move(partitions));
|
|
}
|
|
}
|
|
|
|
DirectoryBlobReader::DirectoryBlobReader(const DirectoryBlobReader& rhs)
|
|
: m_gamecube_pseudopartition(rhs.m_gamecube_pseudopartition),
|
|
m_nonpartition_contents(rhs.m_nonpartition_contents), m_partitions(rhs.m_partitions),
|
|
m_encryption_cache(this), m_is_wii(rhs.m_is_wii), m_encrypted(rhs.m_encrypted),
|
|
m_data_size(rhs.m_data_size),
|
|
m_wrapped_volume(rhs.m_wrapped_volume ?
|
|
CreateDisc(rhs.m_wrapped_volume->GetBlobReader().CopyReader()) :
|
|
nullptr)
|
|
{
|
|
InitFileSlots();
|
|
}
|
|
|
|
void DirectoryBlobReader::InitFileSlots()
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < NUM_FILE_SLOTS; i++)
|
|
{
|
|
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;
|
|
m_file_slots[i].path[0] = '\0';
|
|
}
|
|
}
|
|
|
|
DirectoryBlobReader::~DirectoryBlobReader()
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < NUM_FILE_SLOTS; i++)
|
|
{
|
|
if (m_file_slots[i].file)
|
|
filestream_close(m_file_slots[i].file);
|
|
}
|
|
}
|
|
|
|
bool DirectoryBlobReader::ReadContentFile(const char* name, size_t name_len, u64 offset,
|
|
u8* buffer, u64 size)
|
|
{
|
|
FileSlot* slot;
|
|
bool ok;
|
|
u32 hash = 2166136261u;
|
|
size_t i;
|
|
|
|
for (i = 0; i < name_len; i++)
|
|
hash = (hash ^ u8(name[i])) * 16777619u;
|
|
slot = &m_file_slots[hash % NUM_FILE_SLOTS];
|
|
|
|
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);
|
|
}
|
|
|
|
if (!slot->file || std::strcmp(slot->path, name) != 0)
|
|
{
|
|
int64_t map_size = 0;
|
|
if (slot->file)
|
|
filestream_close(slot->file);
|
|
slot->file = filestream_open(name, RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS);
|
|
slot->map = slot->file ? filestream_get_mapped_ptr(slot->file, &map_size) : nullptr;
|
|
slot->map_size = slot->map ? u64(map_size) : 0;
|
|
std::memcpy(slot->path, name, name_len + 1);
|
|
}
|
|
|
|
if (slot->map)
|
|
{
|
|
ok = offset <= slot->map_size && size <= slot->map_size - offset;
|
|
if (ok)
|
|
std::memcpy(buffer, slot->map + offset, size_t(size));
|
|
}
|
|
else
|
|
{
|
|
ok = slot->file && filestream_seek(slot->file, s64(offset), RETRO_VFS_SEEK_POSITION_START) >= 0 &&
|
|
filestream_read(slot->file, buffer, s64(size)) == s64(size);
|
|
}
|
|
|
|
retro_atomic_store_release_int(&slot->busy, 0);
|
|
return ok;
|
|
}
|
|
|
|
bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer)
|
|
{
|
|
if (offset + length > m_data_size)
|
|
return false;
|
|
|
|
return (m_is_wii ? m_nonpartition_contents : m_gamecube_pseudopartition.GetContents())
|
|
.Read(offset, length, buffer, this);
|
|
}
|
|
|
|
const DirectoryBlobPartition* DirectoryBlobReader::GetPartition(u64 offset, u64 size,
|
|
u64 partition_data_offset) const
|
|
{
|
|
const auto it = m_partitions.find(partition_data_offset);
|
|
if (it == m_partitions.end())
|
|
return nullptr;
|
|
|
|
if (offset + size > it->second.GetDataSize())
|
|
return nullptr;
|
|
|
|
return &it->second;
|
|
}
|
|
|
|
bool DirectoryBlobReader::SupportsReadWiiDecrypted(u64 offset, u64 size,
|
|
u64 partition_data_offset) const
|
|
{
|
|
return static_cast<bool>(GetPartition(offset, size, partition_data_offset));
|
|
}
|
|
|
|
bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer,
|
|
u64 partition_data_offset)
|
|
{
|
|
const DirectoryBlobPartition* partition = GetPartition(offset, size, partition_data_offset);
|
|
if (!partition)
|
|
return false;
|
|
|
|
return partition->GetContents().Read(offset, size, buffer, this);
|
|
}
|
|
|
|
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, this);
|
|
|
|
return m_encryption_cache.EncryptGroups(offset, size, buffer, partition_data_offset,
|
|
partition_data_decrypted_size, it->second.GetKey());
|
|
}
|
|
|
|
BlobType DirectoryBlobReader::GetBlobType() const
|
|
{
|
|
return BlobType::DIRECTORY;
|
|
}
|
|
|
|
std::unique_ptr<BlobReader> DirectoryBlobReader::CopyReader() const
|
|
{
|
|
return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(*this));
|
|
}
|
|
|
|
u64 DirectoryBlobReader::GetRawSize() const
|
|
{
|
|
// Not implemented
|
|
return 0;
|
|
}
|
|
|
|
u64 DirectoryBlobReader::GetDataSize() const
|
|
{
|
|
return m_data_size;
|
|
}
|
|
|
|
void DirectoryBlobReader::SetNonpartitionDiscHeaderFromFile(const u8* partition_header,
|
|
const std::string& game_partition_root)
|
|
{
|
|
u8 header_bin[WII_NONPARTITION_DISCHEADER_SIZE];
|
|
const size_t header_bin_size = ReadFileToBuffer(game_partition_root + "disc/header.bin",
|
|
header_bin, sizeof(header_bin));
|
|
SetNonpartitionDiscHeader(partition_header, header_bin, header_bin_size);
|
|
}
|
|
|
|
void DirectoryBlobReader::SetNonpartitionDiscHeader(const u8* partition_header,
|
|
const u8* header_bin, size_t header_bin_size)
|
|
{
|
|
ContentBlob* blob = ContentBlobAlloc(WII_NONPARTITION_DISCHEADER_SIZE);
|
|
u8* header;
|
|
if (!blob)
|
|
return;
|
|
header = ContentBlobData(blob);
|
|
|
|
// If header.bin is missing or smaller than expected, use the content of sys/boot.bin instead
|
|
std::memcpy(header, header_bin, header_bin_size);
|
|
std::memcpy(header + header_bin_size, partition_header + header_bin_size,
|
|
WII_NONPARTITION_DISCHEADER_SIZE - header_bin_size);
|
|
|
|
// 0x60 and 0x61 are the only differences between the partition and non-partition headers
|
|
if (header_bin_size < 0x60)
|
|
header[0x60] = 0;
|
|
if (header_bin_size < 0x61)
|
|
header[0x61] = 0;
|
|
|
|
m_encrypted = (header[0x60] | header[0x61] | header[0x62] | header[0x63]) == 0;
|
|
|
|
m_nonpartition_contents.Add(WII_NONPARTITION_DISCHEADER_ADDRESS,
|
|
WII_NONPARTITION_DISCHEADER_SIZE, ContentMemory(blob, 0));
|
|
}
|
|
|
|
void DirectoryBlobReader::SetWiiRegionDataFromFile(const std::string& game_partition_root)
|
|
{
|
|
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 =
|
|
ReadFileToBuffer(region_bin_path, wii_region_data, sizeof(wii_region_data));
|
|
SetWiiRegionData(wii_region_data, bytes_read, region_bin_path);
|
|
}
|
|
|
|
void DirectoryBlobReader::SetWiiRegionData(const u8* wii_region_data, size_t size,
|
|
const std::string& log_path)
|
|
{
|
|
ContentBlob* blob = ContentBlobAlloc(WII_REGION_DATA_SIZE);
|
|
u8* region_data;
|
|
if (!blob)
|
|
return;
|
|
region_data = ContentBlobData(blob);
|
|
std::memset(region_data, 0, 0x10);
|
|
std::memset(region_data + 0x10, 0x80, WII_REGION_DATA_SIZE - 0x10);
|
|
Write32(INVALID_REGION, 0, region_data);
|
|
|
|
std::memcpy(region_data, wii_region_data,
|
|
size < WII_REGION_DATA_SIZE ? size_t(size) : size_t(WII_REGION_DATA_SIZE));
|
|
|
|
if (size < 0x4)
|
|
ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", log_path);
|
|
else if (size < 0x20)
|
|
ERROR_LOG_FMT(DISCIO, "Couldn't read age ratings from {}", log_path);
|
|
|
|
m_nonpartition_contents.Add(WII_REGION_DATA_ADDRESS, WII_REGION_DATA_SIZE,
|
|
ContentMemory(blob, 0));
|
|
}
|
|
|
|
static bool PartitionLess(const DirectoryBlobReader::PartitionWithType& lhs,
|
|
const DirectoryBlobReader::PartitionWithType& rhs)
|
|
{
|
|
if (lhs.type == rhs.type)
|
|
return lhs.partition.GetRootDirectory() < rhs.partition.GetRootDirectory();
|
|
|
|
// Ascending sort by partition type, except Update (1) comes before before Game (0)
|
|
return (lhs.type > PartitionType::Update || rhs.type > PartitionType::Update) ?
|
|
lhs.type < rhs.type :
|
|
lhs.type > rhs.type;
|
|
}
|
|
|
|
void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partitions)
|
|
{
|
|
// a handful of partitions: insertion sort
|
|
for (size_t i = 1; i < partitions.size(); i++)
|
|
{
|
|
for (size_t j = i; j > 0 && PartitionLess(partitions[j], partitions[j - 1]); j--)
|
|
std::swap(partitions[j], partitions[j - 1]);
|
|
}
|
|
|
|
u32 subtable_1_size = 0;
|
|
while (subtable_1_size < partitions.size() && subtable_1_size < 3 &&
|
|
partitions[subtable_1_size].type <= PartitionType::Channel)
|
|
{
|
|
++subtable_1_size;
|
|
}
|
|
const u32 subtable_2_size = static_cast<u32>(partitions.size() - subtable_1_size);
|
|
|
|
constexpr u32 PARTITION_TABLE_ADDRESS = 0x40000;
|
|
constexpr u32 PARTITION_SUBTABLE1_OFFSET = 0x20;
|
|
constexpr u32 PARTITION_SUBTABLE2_OFFSET = 0x40;
|
|
const size_t partition_table_size = PARTITION_SUBTABLE2_OFFSET + subtable_2_size * 8;
|
|
ContentBlob* const table_blob = ZeroedBlob(partition_table_size);
|
|
if (!table_blob)
|
|
return;
|
|
u8* const partition_table = ContentBlobData(table_blob);
|
|
|
|
Write32(subtable_1_size, 0x0, partition_table);
|
|
Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE1_OFFSET) >> 2, 0x4, partition_table);
|
|
if (subtable_2_size != 0)
|
|
{
|
|
Write32(subtable_2_size, 0x8, partition_table);
|
|
Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE2_OFFSET) >> 2, 0xC, partition_table);
|
|
}
|
|
|
|
constexpr u64 STANDARD_UPDATE_PARTITION_ADDRESS = 0x50000;
|
|
constexpr u64 STANDARD_GAME_PARTITION_ADDRESS = 0xF800000;
|
|
u64 partition_address = STANDARD_UPDATE_PARTITION_ADDRESS;
|
|
u64 offset_in_table = PARTITION_SUBTABLE1_OFFSET;
|
|
for (size_t i = 0; i < partitions.size(); ++i)
|
|
{
|
|
if (i == subtable_1_size)
|
|
offset_in_table = PARTITION_SUBTABLE2_OFFSET;
|
|
|
|
if (partitions[i].type == PartitionType::Game)
|
|
{
|
|
if (partition_address < STANDARD_GAME_PARTITION_ADDRESS)
|
|
partition_address = STANDARD_GAME_PARTITION_ADDRESS;
|
|
}
|
|
|
|
Write32(static_cast<u32>(partition_address >> 2), u32(offset_in_table), partition_table);
|
|
offset_in_table += 4;
|
|
Write32(static_cast<u32>(partitions[i].type), u32(offset_in_table), partition_table);
|
|
offset_in_table += 4;
|
|
|
|
SetPartitionHeader(&partitions[i].partition, partition_address);
|
|
|
|
const u64 data_size =
|
|
Common::AlignUp(partitions[i].partition.GetDataSize(), VolumeWii::BLOCK_DATA_SIZE);
|
|
partitions[i].partition.SetDataSize(data_size);
|
|
const u64 encrypted_data_size =
|
|
(data_size / VolumeWii::BLOCK_DATA_SIZE) * VolumeWii::BLOCK_TOTAL_SIZE;
|
|
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));
|
|
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, partition_table_size,
|
|
ContentMemory(table_blob, 0));
|
|
}
|
|
|
|
// This function sets the header that's shortly before the start of the encrypted
|
|
// area, not the header that's right at the beginning of the encrypted area
|
|
void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
|
|
u64 partition_address)
|
|
{
|
|
constexpr u32 TMD_OFFSET = 0x2c0;
|
|
constexpr u32 H3_OFFSET = 0x4000;
|
|
|
|
const std::optional<Partition>& wrapped_partition = partition->GetWrappedPartition();
|
|
const std::string& partition_root = partition->GetRootDirectory();
|
|
|
|
u64 ticket_size;
|
|
if (wrapped_partition)
|
|
{
|
|
size_t new_ticket_size;
|
|
const u8* const new_ticket =
|
|
m_wrapped_volume->GetTicket(*wrapped_partition).GetBytes(&new_ticket_size);
|
|
ticket_size = new_ticket_size < WII_PARTITION_TICKET_SIZE ? new_ticket_size :
|
|
WII_PARTITION_TICKET_SIZE;
|
|
m_nonpartition_contents.AddBytes(partition_address + WII_PARTITION_TICKET_ADDRESS, new_ticket,
|
|
size_t(ticket_size));
|
|
}
|
|
else
|
|
{
|
|
ticket_size = m_nonpartition_contents.CheckSizeAndAdd(
|
|
partition_address + WII_PARTITION_TICKET_ADDRESS, WII_PARTITION_TICKET_SIZE,
|
|
partition_root + "ticket.bin");
|
|
}
|
|
|
|
u64 tmd_size;
|
|
if (wrapped_partition)
|
|
{
|
|
size_t new_tmd_size;
|
|
const u8* const new_tmd =
|
|
m_wrapped_volume->GetTMD(*wrapped_partition).GetBytes(&new_tmd_size);
|
|
tmd_size = new_tmd_size < IOS::ES::MAX_TMD_SIZE ? new_tmd_size : IOS::ES::MAX_TMD_SIZE;
|
|
m_nonpartition_contents.AddBytes(partition_address + TMD_OFFSET, new_tmd, size_t(tmd_size));
|
|
}
|
|
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;
|
|
|
|
u64 cert_size;
|
|
if (wrapped_partition)
|
|
{
|
|
std::vector<u8> new_cert = m_wrapped_volume->GetCertificateChain(*wrapped_partition);
|
|
if (new_cert.size() > max_cert_size)
|
|
new_cert.resize(max_cert_size);
|
|
cert_size = new_cert.size();
|
|
m_nonpartition_contents.AddBytes(partition_address + cert_offset, new_cert.data(), new_cert.size());
|
|
}
|
|
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->HasWiiHashes())
|
|
{
|
|
const std::optional<u64> offset = m_wrapped_volume->ReadSwappedAndShifted(
|
|
wrapped_partition->offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE);
|
|
if (offset)
|
|
{
|
|
ContentBlob* const h3 = ContentBlobAlloc(WII_PARTITION_H3_SIZE);
|
|
if (h3 && m_wrapped_volume->Read(wrapped_partition->offset + *offset,
|
|
WII_PARTITION_H3_SIZE, ContentBlobData(h3),
|
|
PARTITION_NONE))
|
|
{
|
|
m_nonpartition_contents.Add(partition_address + H3_OFFSET, WII_PARTITION_H3_SIZE,
|
|
ContentMemory(h3, 0));
|
|
}
|
|
else if (h3)
|
|
{
|
|
ContentBlobRelease(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;
|
|
u8 partition_header[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);
|
|
Write32(static_cast<u32>(cert_offset >> 2), 0x0C, partition_header);
|
|
Write32(H3_OFFSET >> 2, 0x10, partition_header);
|
|
Write32(PARTITION_DATA_OFFSET >> 2, 0x14, partition_header);
|
|
Write32(static_cast<u32>(data_size >> 2), 0x18, partition_header);
|
|
|
|
m_nonpartition_contents.AddBytes(partition_address + WII_PARTITION_TICKET_SIZE,
|
|
partition_header, sizeof(partition_header));
|
|
|
|
// read straight into the reader
|
|
IOS::ES::TicketReader ticket;
|
|
u8* const ticket_bytes = ticket.ResizeBytes(ticket_size);
|
|
if (ticket_bytes)
|
|
{
|
|
m_nonpartition_contents.Read(partition_address + WII_PARTITION_TICKET_ADDRESS, ticket_size,
|
|
ticket_bytes, this);
|
|
}
|
|
if (ticket.IsValid())
|
|
partition->SetKey(ticket.GetTitleKey());
|
|
}
|
|
|
|
static void GenerateBuilderNodesFromFileSystem(const VolumeDisc& volume, const 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(), partition)});
|
|
nodes->emplace_back(
|
|
FSTBuilderNode{file_info.GetName(), file_info.GetSize(), std::move(source)});
|
|
}
|
|
}
|
|
}
|
|
|
|
DirectoryBlobPartition::DirectoryBlobPartition(std::string root_directory,
|
|
std::optional<bool> is_wii)
|
|
: m_root_directory(std::move(root_directory))
|
|
{
|
|
// the header is put together in place and added once the DOL and FST
|
|
// addresses are in it
|
|
ContentBlob* const header_blob = ZeroedBlob(DISCHEADER_SIZE);
|
|
if (!header_blob)
|
|
return;
|
|
u8* const disc_header = ContentBlobData(header_blob);
|
|
if (ReadFileToBuffer(m_root_directory + "sys/boot.bin", disc_header, DISCHEADER_SIZE) < 0x20)
|
|
ERROR_LOG_FMT(DISCIO, "{} doesn't exist or is too small", m_root_directory + "sys/boot.bin");
|
|
|
|
SetDiscType(is_wii, disc_header);
|
|
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, disc_header);
|
|
BuildFSTFromFolder(m_root_directory + "files/", fst_address, disc_header);
|
|
|
|
m_contents.Add(DISCHEADER_ADDRESS, DISCHEADER_SIZE, ContentMemory(header_blob, 0));
|
|
}
|
|
|
|
// The node becomes one file of the blob's bytes; takes over the reference.
|
|
static void FillSingleFileNode(FSTBuilderNode* node, ContentBlob* blob)
|
|
{
|
|
std::vector<BuilderContentSource> contents;
|
|
const u64 size = blob ? blob->size : 0;
|
|
if (size)
|
|
contents.emplace_back(BuilderContentSource{0, size, ContentMemory(blob, 0)});
|
|
else if (blob)
|
|
ContentBlobRelease(blob);
|
|
node->m_size = size;
|
|
node->m_content = std::move(contents);
|
|
}
|
|
|
|
// size octets of the partition at offset in a blob; an empty blob when the
|
|
// read fails
|
|
static ContentBlob* ReadVolumeToBlob(VolumeDisc* volume, const Partition& partition, u64 offset,
|
|
size_t size)
|
|
{
|
|
ContentBlob* blob = ContentBlobAlloc(size);
|
|
if (blob && !volume->Read(offset, size, ContentBlobData(blob), partition))
|
|
blob->size = 0;
|
|
return blob;
|
|
}
|
|
|
|
// The node tagged userdata, read into a blob of size octets, zero padded;
|
|
// of the node's own size when size is 0. *node_size gets the node's size.
|
|
static ContentBlob* ExtractNodeToBlob(std::vector<FSTBuilderNode>* nodes, void* userdata,
|
|
DirectoryBlobReader* reader, size_t size, u64* node_size)
|
|
{
|
|
ContentBlob* blob;
|
|
size_t n = 0;
|
|
size_t i;
|
|
const FSTBuilderNode* node = nullptr;
|
|
|
|
for (i = 0; i < nodes->size(); i++)
|
|
{
|
|
if ((*nodes)[i].m_user_data == userdata)
|
|
{
|
|
node = &(*nodes)[i];
|
|
break;
|
|
}
|
|
}
|
|
if (node && node->IsFile())
|
|
n = size_t(node->m_size);
|
|
if (node_size)
|
|
*node_size = n;
|
|
if (!size)
|
|
size = n;
|
|
if (n > size)
|
|
n = size;
|
|
|
|
blob = ContentBlobAlloc(size);
|
|
if (!blob)
|
|
return nullptr;
|
|
if (n)
|
|
{
|
|
DiscContentContainer tmp;
|
|
for (const BuilderContentSource& content : node->GetFileContent())
|
|
tmp.Add(content.m_offset, content.m_size, content.m_source);
|
|
tmp.Read(0, n, ContentBlobData(blob), reader);
|
|
}
|
|
std::memset(ContentBlobData(blob) + n, 0, size - n);
|
|
return blob;
|
|
}
|
|
|
|
DirectoryBlobPartition::DirectoryBlobPartition(
|
|
VolumeDisc* volume, const Partition& partition, std::optional<bool> is_wii,
|
|
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes)>& sys_callback,
|
|
const std::function<void(std::vector<FSTBuilderNode>* fst_nodes, FSTBuilderNode* dol_node)>&
|
|
fst_callback,
|
|
DirectoryBlobReader* blob)
|
|
: m_is_triforce(volume && volume->GetVolumeType() == Platform::Triforce),
|
|
m_wrapped_partition(partition)
|
|
{
|
|
std::vector<FSTBuilderNode> sys_nodes;
|
|
// the sys nodes' tags: what a callback replaces is found by them
|
|
static char boot_tag, bi2_tag, apploader_tag;
|
|
ContentBlob* sys_blob;
|
|
u64 bi2_node_size = 0;
|
|
|
|
sys_nodes.emplace_back(FSTBuilderNode{"boot.bin", 0, {}, &boot_tag});
|
|
FillSingleFileNode(&sys_nodes.back(),
|
|
ReadVolumeToBlob(volume, partition, DISCHEADER_ADDRESS, DISCHEADER_SIZE));
|
|
|
|
sys_nodes.emplace_back(FSTBuilderNode{"bi2.bin", 0, {}, &bi2_tag});
|
|
FillSingleFileNode(&sys_nodes.back(), ReadVolumeToBlob(volume, partition, BI2_ADDRESS, BI2_SIZE));
|
|
|
|
const auto apploader_size = GetApploaderSize(*volume, partition);
|
|
sys_nodes.emplace_back(FSTBuilderNode{"apploader.img", 0, {}, &apploader_tag});
|
|
if (apploader_size)
|
|
{
|
|
FillSingleFileNode(&sys_nodes.back(), ReadVolumeToBlob(volume, partition, APPLOADER_ADDRESS,
|
|
size_t(*apploader_size)));
|
|
}
|
|
|
|
if (sys_callback)
|
|
sys_callback(&sys_nodes);
|
|
|
|
// the header is put together in place and added once the DOL and FST
|
|
// addresses are in it
|
|
ContentBlob* const header_blob =
|
|
ExtractNodeToBlob(&sys_nodes, &boot_tag, blob, DISCHEADER_SIZE, nullptr);
|
|
if (!header_blob)
|
|
return;
|
|
u8* const disc_header = ContentBlobData(header_blob);
|
|
SetDiscType(is_wii, disc_header);
|
|
sys_blob = ExtractNodeToBlob(&sys_nodes, &bi2_tag, blob, BI2_SIZE, &bi2_node_size);
|
|
if (sys_blob)
|
|
SetBI2(sys_blob, bi2_node_size);
|
|
const u64 new_dol_address =
|
|
SetApploader(ExtractNodeToBlob(&sys_nodes, &apploader_tag, blob, 0, nullptr), "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, 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, disc_header);
|
|
BuildFST(std::move(nodes), new_fst_address, disc_header);
|
|
|
|
m_contents.Add(DISCHEADER_ADDRESS, DISCHEADER_SIZE, ContentMemory(header_blob, 0));
|
|
}
|
|
|
|
void DirectoryBlobPartition::SetDiscType(std::optional<bool> is_wii, const u8* disc_header)
|
|
{
|
|
if (is_wii.has_value())
|
|
{
|
|
m_is_wii = *is_wii;
|
|
}
|
|
else
|
|
{
|
|
m_is_wii = Common::swap32(&disc_header[0x18]) == WII_DISC_MAGIC;
|
|
const bool is_gc = Common::swap32(&disc_header[0x1c]) == GAMECUBE_DISC_MAGIC;
|
|
if (m_is_wii == is_gc)
|
|
{
|
|
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::SetBI2FromFile(const std::string& bi2_path)
|
|
{
|
|
ContentBlob* const blob = ZeroedBlob(BI2_SIZE);
|
|
size_t bytes_read;
|
|
if (!blob)
|
|
return;
|
|
|
|
if (!m_is_wii)
|
|
Write32(INVALID_REGION, 0x18, ContentBlobData(blob));
|
|
|
|
bytes_read = ReadFileToBuffer(bi2_path, ContentBlobData(blob), BI2_SIZE);
|
|
if (!m_is_wii && bytes_read < 0x1C)
|
|
ERROR_LOG_FMT(DISCIO, "Couldn't read region from {}", bi2_path);
|
|
|
|
m_contents.Add(BI2_ADDRESS, BI2_SIZE, ContentMemory(blob, 0));
|
|
}
|
|
|
|
void DirectoryBlobPartition::SetBI2(ContentBlob* bi2, u64 bi2_size)
|
|
{
|
|
if (!m_is_wii && bi2_size < 0x1C)
|
|
Write32(INVALID_REGION, 0x18, ContentBlobData(bi2));
|
|
|
|
m_contents.Add(BI2_ADDRESS, bi2->size, ContentMemory(bi2, 0));
|
|
}
|
|
|
|
u64 DirectoryBlobPartition::SetApploaderFromFile(const std::string& path)
|
|
{
|
|
File::DirectIOFile file(path, File::AccessMode::Read);
|
|
const size_t size = size_t(file.GetSize());
|
|
ContentBlob* blob = ContentBlobAlloc(size);
|
|
if (blob && size && !file.OffsetRead(0, ContentBlobData(blob), size))
|
|
blob->size = 0;
|
|
return SetApploader(blob, path);
|
|
}
|
|
|
|
u64 DirectoryBlobPartition::SetApploader(ContentBlob* apploader, const std::string& log_path)
|
|
{
|
|
bool success = false;
|
|
const u64 size = apploader ? apploader->size : 0;
|
|
|
|
if (size < 0x20)
|
|
{
|
|
ERROR_LOG_FMT(DISCIO, "{} couldn't be accessed or is too small", log_path);
|
|
}
|
|
else
|
|
{
|
|
const u8* const data = ContentBlobData(apploader);
|
|
const u64 apploader_size = 0x20 + Common::swap32(data + 0x14) + Common::swap32(data + 0x18);
|
|
if (apploader_size != size)
|
|
ERROR_LOG_FMT(DISCIO, "{} is the wrong size... Is it really an apploader?", log_path);
|
|
else
|
|
success = true;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
// the header alone, made sure BS2 HLE doesn't try to run the apploader
|
|
ContentBlob* const header = ZeroedBlob(0x20);
|
|
if (apploader)
|
|
{
|
|
if (header)
|
|
std::memcpy(ContentBlobData(header), ContentBlobData(apploader),
|
|
size < 0x20 ? size_t(size) : size_t(0x20));
|
|
ContentBlobRelease(apploader);
|
|
}
|
|
apploader = header;
|
|
if (!apploader)
|
|
return Common::AlignUp(APPLOADER_ADDRESS + 0x20 + 0x20, 0x20ull);
|
|
Write32(static_cast<u32>(-1), 0x10, ContentBlobData(apploader));
|
|
}
|
|
|
|
const u64 apploader_size = apploader->size;
|
|
m_contents.Add(APPLOADER_ADDRESS, apploader_size, ContentMemory(apploader, 0));
|
|
|
|
// Return DOL address, 32 byte aligned (plus 32 byte padding)
|
|
return Common::AlignUp(APPLOADER_ADDRESS + apploader_size + 0x20, 0x20ull);
|
|
}
|
|
|
|
u64 DirectoryBlobPartition::SetDOLFromFile(const std::string& path, u64 dol_address,
|
|
u8* disc_header)
|
|
{
|
|
const u64 dol_size = m_contents.CheckSizeAndAdd(dol_address, path);
|
|
|
|
Write32(static_cast<u32>(dol_address >> m_address_shift), 0x0420, disc_header);
|
|
|
|
// Return FST address, 32 byte aligned (plus 32 byte padding)
|
|
return Common::AlignUp(dol_address + dol_size + 0x20, 0x20ull);
|
|
}
|
|
|
|
u64 DirectoryBlobPartition::SetDOL(FSTBuilderNode dol_node, u64 dol_address,
|
|
u8* disc_header)
|
|
{
|
|
for (auto& content : dol_node.GetFileContent())
|
|
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);
|
|
|
|
// 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,
|
|
u8* disc_header)
|
|
{
|
|
auto nodes = ConvertFSTEntriesToBuilderNodes(File::ScanDirectoryTree(fst_root_path, true));
|
|
BuildFST(std::move(nodes), fst_address, disc_header);
|
|
}
|
|
|
|
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 FSTBuilderNode* files, size_t count)
|
|
{
|
|
u32 name_size = 0;
|
|
for (size_t i = 0; i < count; i++)
|
|
{
|
|
const FSTBuilderNode& entry = files[i];
|
|
if (entry.IsFolder())
|
|
{
|
|
const std::vector<FSTBuilderNode>& children = entry.GetFolderContent();
|
|
name_size += ComputeNameSize(children.data(), children.size());
|
|
}
|
|
name_size += static_cast<u32>(entry.m_filename.length() + 1);
|
|
}
|
|
return name_size;
|
|
}
|
|
|
|
// Upper-cased ASCII order, then plain order, compared in place.
|
|
static bool NameLess(const FSTBuilderNode& one, const FSTBuilderNode& two)
|
|
{
|
|
const unsigned char* a = reinterpret_cast<const unsigned char*>(one.m_filename.c_str());
|
|
const unsigned char* b = reinterpret_cast<const unsigned char*>(two.m_filename.c_str());
|
|
size_t i;
|
|
for (i = 0;; i++)
|
|
{
|
|
unsigned ca = a[i], cb = b[i];
|
|
if (ca >= 'a' && ca <= 'z')
|
|
ca -= 'a' - 'A';
|
|
if (cb >= 'a' && cb <= 'z')
|
|
cb -= 'a' - 'A';
|
|
if (ca != cb)
|
|
return ca < cb;
|
|
if (!a[i])
|
|
break;
|
|
}
|
|
return one.m_filename < two.m_filename;
|
|
}
|
|
|
|
// In-place heap sort: no allocation, O(n log n); names are unique so
|
|
// stability does not matter.
|
|
static void SiftDown(FSTBuilderNode* nodes, size_t root, size_t count)
|
|
{
|
|
for (;;)
|
|
{
|
|
size_t child = root * 2 + 1;
|
|
if (child >= count)
|
|
return;
|
|
if (child + 1 < count && NameLess(nodes[child], nodes[child + 1]))
|
|
child++;
|
|
if (!NameLess(nodes[root], nodes[child]))
|
|
return;
|
|
std::swap(nodes[root], nodes[child]);
|
|
root = child;
|
|
}
|
|
}
|
|
|
|
static void SortByName(FSTBuilderNode* nodes, size_t count)
|
|
{
|
|
size_t i;
|
|
if (count < 2)
|
|
return;
|
|
for (i = count / 2; i > 0; i--)
|
|
SiftDown(nodes, i - 1, count);
|
|
for (i = count - 1; i > 0; i--)
|
|
{
|
|
std::swap(nodes[0], nodes[i]);
|
|
SiftDown(nodes, 0, i);
|
|
}
|
|
}
|
|
|
|
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,
|
|
u8* disc_header)
|
|
{
|
|
ConvertUTF8NamesToSHIFTJIS(&root_nodes);
|
|
|
|
u32 name_table_size = Common::AlignUp(ComputeNameSize(root_nodes.data(), root_nodes.size()), 1ull << m_address_shift);
|
|
|
|
// 1 extra for the root entry
|
|
u64 total_entries = RecalculateFolderSizes(&root_nodes) + 1;
|
|
|
|
const u64 name_table_offset = total_entries * ENTRY_SIZE;
|
|
// built in place, then added as it stands
|
|
const u64 fst_size = name_table_offset + name_table_size;
|
|
ContentBlob* const fst_blob = ZeroedBlob(size_t(fst_size));
|
|
if (!fst_blob)
|
|
return;
|
|
u8* const fst_data = ContentBlobData(fst_blob);
|
|
|
|
// 32 KiB aligned start of data on disc
|
|
u64 current_data_address = Common::AlignUp(fst_address + fst_size, 0x8000ull);
|
|
|
|
u32 fst_offset = 0; // Offset within FST data
|
|
u32 name_offset = 0; // Offset within name table
|
|
u32 root_offset = 0; // Offset of root of FST
|
|
|
|
// write root entry
|
|
WriteEntryData(fst_data, &fst_offset, DIRECTORY_ENTRY, 0, 0, total_entries, m_address_shift);
|
|
|
|
WriteDirectory(fst_data, &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
|
|
ASSERT(Common::AlignUp(name_offset, 1ull << m_address_shift) == name_table_size);
|
|
|
|
// write FST size and location
|
|
Write32((u32)(fst_address >> m_address_shift), 0x0424, disc_header);
|
|
Write32((u32)(fst_size >> m_address_shift), 0x0428, disc_header);
|
|
Write32((u32)(fst_size >> m_address_shift), 0x042c, disc_header);
|
|
|
|
m_contents.Add(fst_address, fst_size, ContentMemory(fst_blob, 0));
|
|
|
|
m_data_size = current_data_address;
|
|
}
|
|
|
|
void DirectoryBlobPartition::WriteEntryData(u8* fst_data, u32* entry_offset, u8 type,
|
|
u32 name_offset, u64 data_offset, u64 length,
|
|
u32 address_shift)
|
|
{
|
|
fst_data[(*entry_offset)++] = type;
|
|
|
|
fst_data[(*entry_offset)++] = (name_offset >> 16) & 0xff;
|
|
fst_data[(*entry_offset)++] = (name_offset >> 8) & 0xff;
|
|
fst_data[(*entry_offset)++] = (name_offset) & 0xff;
|
|
|
|
Write32((u32)(data_offset >> address_shift), *entry_offset, fst_data);
|
|
*entry_offset += 4;
|
|
|
|
Write32((u32)length, *entry_offset, fst_data);
|
|
*entry_offset += 4;
|
|
}
|
|
|
|
void DirectoryBlobPartition::WriteEntryName(u8* fst_data, u32* name_offset,
|
|
const std::string& name, u64 name_table_offset)
|
|
{
|
|
std::memcpy(fst_data + *name_offset + name_table_offset, name.c_str(), name.length() + 1);
|
|
|
|
*name_offset += (u32)(name.length() + 1);
|
|
}
|
|
|
|
void DirectoryBlobPartition::WriteDirectory(u8* fst_data,
|
|
std::vector<FSTBuilderNode>* parent_entries,
|
|
u32* fst_offset, u32* name_offset, u64* data_offset,
|
|
u32 parent_entry_index, u64 name_table_offset)
|
|
{
|
|
std::vector<FSTBuilderNode>& sorted_entries = *parent_entries;
|
|
|
|
// Sort for determinism
|
|
SortByName(sorted_entries.data(), sorted_entries.size());
|
|
|
|
for (FSTBuilderNode& entry : sorted_entries)
|
|
{
|
|
if (entry.IsFolder())
|
|
{
|
|
u32 entry_index = *fst_offset / ENTRY_SIZE;
|
|
WriteEntryData(fst_data, fst_offset, DIRECTORY_ENTRY, *name_offset, parent_entry_index,
|
|
entry_index + entry.m_size + 1, 0);
|
|
WriteEntryName(fst_data, name_offset, entry.m_filename, name_table_offset);
|
|
|
|
auto& child_nodes = entry.GetFolderContent();
|
|
WriteDirectory(fst_data, &child_nodes, fst_offset, name_offset, data_offset, entry_index,
|
|
name_table_offset);
|
|
}
|
|
else
|
|
{
|
|
// Skip Triforce DIMM so the file doesn't overlap with it. Any portion of
|
|
// a file placed in 0x1F000000-0x1F800000 would be shadowed by SRAM on the
|
|
// AMMediaboard and read back as DIMM contents instead of disc data.
|
|
if (m_is_triforce && *data_offset < 0x1F800000 && *data_offset + entry.m_size > 0x1F000000)
|
|
{
|
|
*data_offset = 0x1F800000ull;
|
|
}
|
|
|
|
// put entry in FST
|
|
WriteEntryData(fst_data, fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.m_size,
|
|
m_address_shift);
|
|
WriteEntryName(fst_data, name_offset, entry.m_filename, name_table_offset);
|
|
|
|
// write entry to virtual disc
|
|
auto& contents = entry.GetFileContent();
|
|
for (BuilderContentSource& content : contents)
|
|
{
|
|
m_contents.Add(*data_offset + content.m_offset, content.m_size,
|
|
content.m_source);
|
|
}
|
|
|
|
// For most files, an alignment of 0x20 is enough. But some files need 0x8000,
|
|
// in particular all DTK audio files in GameCube games.
|
|
// TODO: Do any Wii games need 0x8000 alignment?
|
|
const u64 data_alignment = m_is_triforce ? 0x20ull : 0x8000ull;
|
|
*data_offset = Common::AlignUp(*data_offset + entry.m_size, data_alignment);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Up to size octets of the file at path into buffer; returns how many.
|
|
static size_t ReadFileToBuffer(const std::string& path, u8* buffer, size_t size)
|
|
{
|
|
File::DirectIOFile file(path, File::AccessMode::Read);
|
|
const u64 file_size = file.GetSize();
|
|
const size_t n = file_size < size ? size_t(file_size) : size;
|
|
return n && file.OffsetRead(0, buffer, n) ? n : 0;
|
|
}
|
|
|
|
static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer)
|
|
{
|
|
if (start_address > *address && *length > 0)
|
|
{
|
|
u64 padBytes = start_address - *address;
|
|
if (*length < padBytes)
|
|
padBytes = *length;
|
|
memset(*buffer, 0, (size_t)padBytes);
|
|
*length -= padBytes;
|
|
*buffer += padBytes;
|
|
*address += padBytes;
|
|
}
|
|
}
|
|
|
|
static void Write32(u32 data, u32 offset, u8* buffer)
|
|
{
|
|
buffer[offset] = u8(data >> 24);
|
|
buffer[offset + 1] = u8(data >> 16);
|
|
buffer[offset + 2] = u8(data >> 8);
|
|
buffer[offset + 3] = u8(data);
|
|
}
|
|
|
|
} // namespace DiscIO
|