DirectoryBlob: Support multiple partitions

This commit is contained in:
JosJuice 2017-06-11 13:22:13 +02:00
parent 955ab72b1d
commit 34fd08e7eb
2 changed files with 181 additions and 46 deletions

View File

@ -48,10 +48,17 @@ 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
{
Game = 0,
Update = 1,
Channel = 2,
// There are more types used by Super Smash Bros. Brawl, but they don't have special names
};
constexpr u8 ENTRY_SIZE = 0x0c;
constexpr u8 FILE_ENTRY = 0;
constexpr u8 DIRECTORY_ENTRY = 1;
constexpr u64 GAME_PARTITION_ADDRESS = 0x50000;
DiscContent::DiscContent(u64 offset, u64 size, const std::string& path)
: m_offset(offset), m_size(size), m_content_source(path)
@ -110,6 +117,39 @@ bool DiscContent::Read(u64* offset, u64* length, u8** buffer) const
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"
if (std::all_of(name.cbegin() + 1, name.cend(), [](char c) { return c >= '0' && c <= '9'; }))
{
u32 result;
if (TryParse(name.substr(1), &result))
return static_cast<PartitionType>(result);
}
}
return {};
}
static bool PathCharactersEqual(char a, char b)
{
return a == b
@ -137,27 +177,40 @@ static bool PathEndsWith(const std::string& path, const std::string& suffix)
return true;
}
static bool IsValidDirectoryBlob(const std::string& dol_path, std::string* root_directory)
static bool IsValidDirectoryBlob(const std::string& dol_path, std::string* partition_root,
std::string* true_root)
{
if (!PathEndsWith(dol_path, "/sys/main.dol"))
return false;
const size_t chars_to_remove = std::string("sys/main.dol").size();
*root_directory = dol_path.substr(0, dol_path.size() - chars_to_remove);
*partition_root = dol_path.substr(0, dol_path.size() - chars_to_remove);
return File::GetSize(*root_directory + "sys/boot.bin") >= 0x20;
if (File::GetSize(*partition_root + "sys/boot.bin") < 0x20)
return false;
#ifdef _WIN32
constexpr const char* dir_separator = "/\\";
#else
constexpr char dir_separator = '/';
#endif
const size_t true_root_end = dol_path.find_last_of(dir_separator, partition_root->size() - 2) + 1;
*true_root = dol_path.substr(0, true_root_end);
return true;
}
std::unique_ptr<DirectoryBlobReader> DirectoryBlobReader::Create(const std::string& dol_path)
{
std::string root_directory;
if (!IsValidDirectoryBlob(dol_path, &root_directory))
std::string partition_root, true_root;
if (!IsValidDirectoryBlob(dol_path, &partition_root, &true_root))
return nullptr;
return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(root_directory));
return std::unique_ptr<DirectoryBlobReader>(new DirectoryBlobReader(partition_root, true_root));
}
DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root)
DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root,
const std::string& true_root)
{
DirectoryBlobPartition game_partition(game_partition_root, {});
m_is_wii = game_partition.IsWii();
@ -170,16 +223,31 @@ DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root)
else
{
SetNonpartitionDiscHeader(game_partition.GetHeader(), game_partition_root);
const u64 unaligned_data_size = VolumeWii::PartitionOffsetToRawOffset(
game_partition.GetDataSize(), Partition(GAME_PARTITION_ADDRESS));
m_data_size = Common::AlignUp(unaligned_data_size, 0x8000ull);
m_partitions.emplace(GAME_PARTITION_ADDRESS, std::move(game_partition));
SetPartitionTable();
SetWiiRegionData(game_partition_root);
SetTMDAndTicket(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));
}
}
@ -282,17 +350,6 @@ void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector<u8>& parti
m_disk_header_nonpartition.data());
}
void DirectoryBlobReader::SetPartitionTable()
{
constexpr u64 PARTITION_TABLE_ADDRESS = 0x40000;
static const std::array<u32, 10> PARTITION_TABLE = {
{Common::swap32(1), Common::swap32((PARTITION_TABLE_ADDRESS + 0x20) >> 2), 0, 0, 0, 0, 0, 0,
Common::swap32(GAME_PARTITION_ADDRESS >> 2), 0}};
m_nonpartition_contents.emplace(PARTITION_TABLE_ADDRESS, PARTITION_TABLE.size() * sizeof(u32),
reinterpret_cast<const u8*>(PARTITION_TABLE.data()));
}
void DirectoryBlobReader::SetWiiRegionData(const std::string& game_partition_root)
{
m_wii_region_data.resize(0x10, 0x00);
@ -315,19 +372,89 @@ void DirectoryBlobReader::SetWiiRegionData(const std::string& game_partition_roo
m_wii_region_data.data());
}
void DirectoryBlobReader::SetTMDAndTicket(const std::string& partition_root)
void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partitions)
{
std::sort(partitions.begin(), partitions.end(),
[](const PartitionWithType& lhs, const 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;
});
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;
m_partition_table.resize(PARTITION_SUBTABLE2_OFFSET + subtable_2_size * 8);
Write32(subtable_1_size, 0x0, &m_partition_table);
Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE1_OFFSET) >> 2, 0x4, &m_partition_table);
if (subtable_2_size != 0)
{
Write32(subtable_2_size, 0x8, &m_partition_table);
Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE2_OFFSET) >> 2, 0xC, &m_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)
partition_address = std::max(partition_address, STANDARD_GAME_PARTITION_ADDRESS);
Write32(static_cast<u32>(partition_address >> 2), offset_in_table, &m_partition_table);
offset_in_table += 4;
Write32(static_cast<u32>(partitions[i].type), offset_in_table, &m_partition_table);
offset_in_table += 4;
SetTMDAndTicket(partitions[i].partition.GetRootDirectory(), partition_address);
const u64 partition_data_size = partitions[i].partition.GetDataSize();
m_partitions.emplace(partition_address, std::move(partitions[i].partition));
const u64 unaligned_next_partition_address =
VolumeWii::PartitionOffsetToRawOffset(partition_data_size, Partition(partition_address));
partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull);
}
m_data_size = partition_address;
m_nonpartition_contents.emplace(PARTITION_TABLE_ADDRESS, m_partition_table.size(),
m_partition_table.data());
}
void DirectoryBlobReader::SetTMDAndTicket(const std::string& partition_root, u64 partition_address)
{
constexpr u32 TICKET_OFFSET = 0x0;
constexpr u32 TICKET_SIZE = 0x2a4;
constexpr u32 TMD_OFFSET = 0x2c0;
constexpr u32 MAX_TMD_SIZE = 0x49e4;
AddFileToContents(&m_nonpartition_contents, partition_root + "ticket.bin",
GAME_PARTITION_ADDRESS + TICKET_OFFSET, TICKET_SIZE);
partition_address + TICKET_OFFSET, TICKET_SIZE);
const DiscContent& tmd = AddFileToContents(&m_nonpartition_contents, partition_root + "tmd.bin",
GAME_PARTITION_ADDRESS + TMD_OFFSET, MAX_TMD_SIZE);
m_tmd_header = {Common::swap32(static_cast<u32>(tmd.GetSize())), Common::swap32(TMD_OFFSET >> 2)};
m_nonpartition_contents.emplace(GAME_PARTITION_ADDRESS + TICKET_SIZE, sizeof(m_tmd_header),
reinterpret_cast<const u8*>(&m_tmd_header));
partition_address + TMD_OFFSET, MAX_TMD_SIZE);
constexpr u32 TMD_HEADER_SIZE = 8;
m_tmd_headers.emplace_back(TMD_HEADER_SIZE);
std::vector<u8>& tmd_header = m_tmd_headers.back();
Write32(static_cast<u32>(tmd.GetSize()), 0x0, &tmd_header);
Write32(TMD_OFFSET >> 2, 0x4, &tmd_header);
m_nonpartition_contents.emplace(partition_address + TICKET_SIZE, TMD_HEADER_SIZE,
tmd_header.data());
}
DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory,

View File

@ -25,6 +25,8 @@ class IOFile;
namespace DiscIO
{
enum class PartitionType : u32;
class DiscContent
{
public:
@ -67,6 +69,7 @@ public:
bool IsWii() const { return m_is_wii; }
u64 GetDataSize() const { return m_data_size; }
const std::string& GetRootDirectory() const { return m_root_directory; }
const std::vector<u8>& GetHeader() const { return m_disk_header; }
const std::set<DiscContent>& GetContents() const { return m_contents; }
private:
@ -120,15 +123,27 @@ public:
u64 GetDataSize() const override;
private:
explicit DirectoryBlobReader(const std::string& game_partition_root);
struct PartitionWithType
{
PartitionWithType(DirectoryBlobPartition&& partition_, PartitionType type_)
: partition(std::move(partition_)), type(type_)
{
}
DirectoryBlobPartition partition;
PartitionType type;
};
explicit DirectoryBlobReader(const std::string& game_partition_root,
const std::string& true_root);
bool ReadInternal(u64 offset, u64 length, u8* buffer, const std::set<DiscContent>& contents);
void SetNonpartitionDiscHeader(const std::vector<u8>& partition_header,
const std::string& game_partition_root);
void SetPartitionTable();
void SetWiiRegionData(const std::string& game_partition_root);
void SetTMDAndTicket(const std::string& partition_root);
void SetPartitions(std::vector<PartitionWithType>&& partitions);
void SetTMDAndTicket(const std::string& partition_root, u64 partition_address);
// For GameCube:
DirectoryBlobPartition m_gamecube_pseudopartition;
@ -140,16 +155,9 @@ private:
bool m_is_wii;
std::vector<u8> m_disk_header_nonpartition;
std::vector<u8> m_partition_table;
std::vector<u8> m_wii_region_data;
#pragma pack(push, 1)
struct TMDHeader
{
u32 tmd_size;
u32 tmd_offset;
} m_tmd_header;
static_assert(sizeof(TMDHeader) == 8, "Wrong size for TMDHeader");
#pragma pack(pop)
std::vector<std::vector<u8>> m_tmd_headers;
u64 m_data_size;
};