Support EGDF and XSF layouts

This commit is contained in:
Wildhaus 2018-11-27 16:19:51 -05:00 committed by Wildenhaus
parent 80375c62e8
commit 151a955c6a
2 changed files with 287 additions and 204 deletions

View File

@ -60,97 +60,88 @@ StfsContainerDevice::StfsContainerDevice(const std::string& mount_path,
StfsContainerDevice::~StfsContainerDevice() = default;
bool StfsContainerDevice::Initialize() {
// Resolve a valid STFS file if a directory is given.
if (filesystem::IsFolder(local_path_) && !ResolveFromFolder(local_path_)) {
XELOGE("Could not resolve an STFS container given path %s",
local_path_.c_str());
xe::to_string(local_path_).c_str());
return false;
}
if (!filesystem::PathExists(local_path_)) {
XELOGE("STFS container does not exist");
XELOGE("Path to STFS container does not exist: %s",
xe::to_string(local_path_).c_str());
return false;
}
// Map the appropriate file(s)
if (filesystem::PathExists(local_path_ + L".data")) {
// Container is multi-file (GoD)
// Read all datafiles to mapped memory
XELOGI("STFS Container is mutli-file");
// List datafiles and sort by name
auto data_files = filesystem::ListFiles(local_path_ + L".data");
std::sort(data_files.begin(), data_files.end(),
[](filesystem::FileInfo& left, filesystem::FileInfo& right) {
return left.name < right.name;
});
mmap_.clear();
mmap_total_size_ = 0;
for (size_t i = 0; i < data_files.size(); i++) {
auto file = data_files.at(i);
auto path = xe::join_paths(file.path, file.name);
auto map = MappedMemory::Open(path, MappedMemory::Mode::kRead);
mmap_total_size_ += map->size();
mmap_.emplace(std::make_pair(i, std::move(map)));
}
XELOGI("Mapped %d STFS datafiles", mmap_.size());
} else {
// Container is single-file (XBLA)
XELOGI("STFS Container is single-file");
auto map = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
mmap_.emplace(std::make_pair(0, std::move(map)));
}
// Verify successful file mapping
auto map = mmap_.at(0).get();
if (!map) {
XELOGI("STFS container could not be mapped");
return false;
}
// In single-file containers, the header is self-contained.
// In multi-file containers, the header is in the manifest.
auto header_file = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
uint8_t* header_data = (header_file)->data();
auto result = ReadHeaderAndVerify(header_data);
if (result != Error::kSuccess) {
XELOGI("STFS header read/verification failed: %d", result);
// Map the data file(s)
auto map_result = MapFiles();
if (map_result != Error::kSuccess) {
XELOGE("Failed to map STFS container: %d", map_result);
return false;
}
switch (header_.descriptor_type) {
case StfsDescriptorType::kStfs:
result = ReadAllEntriesSTFS(header_data);
return ReadSTFS() == Error::kSuccess;
break;
case StfsDescriptorType::kSvod: {
bool is_gdf = header_.svod_volume_descriptor.device_features &
kFeatureHasEnhancedGDFLayout;
if (is_gdf) {
XELOGI("SVOD uses EGDF Layout.");
const size_t HEADER_SIZE = 0x2000;
base_address_ = HEADER_SIZE;
} else {
XELOGI("SVOD does not use EGDF Layout.");
// If the datafile contains the header, we base after it.
const size_t HEADER_SIZE = 0xB000;
base_address_ = mmap_.size() > 1 ? 0x0 : HEADER_SIZE;
}
result = ReadAllEntriesSVOD();
} break;
case StfsDescriptorType::kSvod:
return ReadSVOD() == Error::kSuccess;
default:
// Shouldn't reach here.
XELOGE("Unknown STFS Descriptor Type: %d", header_.descriptor_type);
return false;
}
}
if (result != Error::kSuccess) {
XELOGE("STFS entry reading failed: %d", result);
return false;
StfsContainerDevice::Error StfsContainerDevice::MapFiles() {
// Map the file containing the STFS Header and read it.
XELOGI("Mapping STFS Header File: %s", xe::to_string(local_path_).c_str());
auto header_map = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
auto header_result = ReadHeaderAndVerify(header_map->data());
if (header_result != Error::kSuccess) {
XELOGE("Error reading STFS Header: %d", header_result);
return header_result;
}
return true;
// If the STFS package is a single file, the header is self contained and
// we don't need to map any extra files.
// NOTE: data_file_count is 0 for STFS and 1 for SVOD
if (header_.data_file_count <= 1) {
XELOGI("STFS container is a single file.");
mmap_.emplace(std::make_pair(0, std::move(header_map)));
return Error::kSuccess;
}
// If the STFS package is multi-file, it is an SVOD system. We need to map
// the files in the .data folder and can discard the header.
auto data_fragment_path = local_path_ + L".data";
if (!filesystem::PathExists(data_fragment_path)) {
XELOGE("STFS container is multi-file, but path %s does not exist.",
xe::to_string(data_fragment_path).c_str());
return Error::kErrorFileMismatch;
}
// Ensure data fragment files are sorted
auto fragment_files = filesystem::ListFiles(data_fragment_path);
std::sort(fragment_files.begin(), fragment_files.end(),
[](filesystem::FileInfo& left, filesystem::FileInfo& right) {
return left.name < right.name;
});
if (fragment_files.size() != header_.data_file_count) {
XELOGE("SVOD expecting %d data fragments, but %d are present.",
header_.data_file_count, fragment_files.size());
return Error::kErrorFileMismatch;
}
for (size_t i = 0; i < fragment_files.size(); i++) {
auto file = fragment_files.at(i);
auto path = xe::join_paths(file.path, file.name);
auto data = MappedMemory::Open(path, MappedMemory::Mode::kRead);
mmap_.emplace(std::make_pair(i, std::move(data)));
}
XELOGI("SVOD successfully mapped %d files.", fragment_files.size());
return Error::kSuccess;
}
void StfsContainerDevice::Dump(StringBuffer* string_buffer) {
@ -207,37 +198,96 @@ StfsContainerDevice::Error StfsContainerDevice::ReadHeaderAndVerify(
return Error::kSuccess;
}
StfsContainerDevice::Error StfsContainerDevice::ReadAllEntriesSVOD() {
// Verify SVOD Magic
const size_t MAGIC_BLOCK = 0x20;
size_t magic_address, magic_file;
BlockToOffsetSVOD(MAGIC_BLOCK, &magic_address, &magic_file);
StfsContainerDevice::Error StfsContainerDevice::ReadSVOD() {
// SVOD Systems can have different layouts. The root block is
// denoted by the magic "MICROSOFT*XBOX*MEDIA" and is always in
// the first "actual" data fragment of the system.
auto data = mmap_.at(0)->data();
const uint8_t* p = data + magic_address;
if (std::memcmp(p, "MICROSOFT*XBOX*MEDIA", 20) != 0) {
return Error::kErrorDamagedFile;
const char* MEDIA_MAGIC = "MICROSOFT*XBOX*MEDIA";
// Check for EDGF layout
auto layout = &header_.svod_volume_descriptor.layout_type;
auto features = header_.svod_volume_descriptor.device_features;
bool has_egdf_layout = features & kFeatureHasEnhancedGDFLayout;
if (has_egdf_layout) {
// The STFS header has specified that this SVOD system uses the EGDF layout.
// We can expect the magic block to be located immediately after the hash
// blocks. We also offset block address calculation by 0x1000 by shifting
// block indices by +0x2.
if (memcmp(data + 0x2000, MEDIA_MAGIC, 20) == 0) {
base_offset_ = 0x0000;
magic_offset_ = 0x2000;
*layout = kEnhancedGDFLayout;
XELOGI("SVOD uses an EGDF layout. Magic block present at 0x2000.");
} else {
XELOGE("SVOD uses an EGDF layout, but the magic block was not found.");
return Error::kErrorFileMismatch;
}
} else if (memcmp(data + 0x12000, MEDIA_MAGIC, 20) == 0) {
// If the SVOD's magic block is at 0x12000, it is likely using an XSF
// layout. This is usually due to converting the game using a third-party
// tool, as most of them use a nulled XSF as a template.
base_offset_ = 0x10000;
magic_offset_ = 0x12000;
// Check for XSF Header
const char* XSF_MAGIC = "XSF";
if (memcmp(data + 0x2000, XSF_MAGIC, 3) == 0) {
*layout = kXSFLayout;
XELOGI("SVOD uses an XSF layout. Magic block present at 0x12000.");
XELOGI("Game was likely converted using a third-party tool.");
} else {
*layout = kUnknownLayout;
XELOGI("SVOD appears to use an XSF layout, but no header is present.");
XELOGI("SVOD magic block found at 0x12000");
}
} else if (memcmp(data + 0xD000, MEDIA_MAGIC, 20) == 0) {
// If the SVOD's magic block is at 0xD000, it most likely means that it is
// a single-file system. The STFS Header is 0xB000 bytes , and the remaining
// 0x2000 is from hash tables. In most cases, these will be STFS, not SVOD.
base_offset_ = 0xB000;
magic_offset_ = 0xD000;
// Check for single file system
if (header_.data_file_count == 1) {
*layout = kSingleFileLayout;
XELOGI("SVOD is a single file. Magic block present at 0xD000.");
} else {
*layout = kUnknownLayout;
XELOGE(
"SVOD is not a single file, but the magic block was found at "
"0xD000.");
}
} else {
XELOGE("Could not locate SVOD magic block.");
return Error::kErrorReadError;
}
// Read Root Entry
uint32_t root_block = xe::load<uint32_t>(p + 0x14);
uint32_t root_size = xe::load<uint32_t>(p + 0x18);
size_t root_address, root_file;
BlockToOffsetSVOD(root_block, &root_address, &root_file);
p = mmap_.at(root_file)->data() + root_address;
// Parse the root directory
uint8_t* magic_block = data + magic_offset_;
uint32_t root_block = xe::load<uint32_t>(magic_block + 0x14);
uint32_t root_size = xe::load<uint32_t>(magic_block + 0x18);
uint32_t root_creation_date = xe::load<uint32_t>(magic_block + 0x1C);
uint32_t root_creation_time = xe::load<uint32_t>(magic_block + 0x20);
uint64_t root_creation_timestamp =
decode_fat_timestamp(root_creation_date, root_creation_time);
auto root_entry = new StfsContainerEntry(this, nullptr, "", &mmap_);
root_entry->attributes_ = kFileAttributeDirectory;
root_entry->access_timestamp_ = root_creation_timestamp;
root_entry->create_timestamp_ = root_creation_timestamp;
root_entry->write_timestamp_ = root_creation_timestamp;
root_entry_ = std::unique_ptr<Entry>(root_entry);
// Traverse all children
return ReadEntrySVOD(root_block, 0, root_entry) ? Error::kSuccess
: Error::kErrorDamagedFile;
// Traverse all child entries
return ReadEntrySVOD(root_block, 0, root_entry);
}
bool StfsContainerDevice::ReadEntrySVOD(uint32_t block, uint32_t ordinal,
StfsContainerEntry* parent) {
StfsContainerDevice::Error StfsContainerDevice::ReadEntrySVOD(
uint32_t block, uint32_t ordinal, StfsContainerEntry* parent) {
// Calculate the file & address of the block
size_t entry_address, entry_file;
BlockToOffsetSVOD(block, &entry_address, &entry_file);
@ -246,17 +296,21 @@ bool StfsContainerDevice::ReadEntrySVOD(uint32_t block, uint32_t ordinal,
// Read block's descriptor
auto data = mmap_.at(entry_file)->data() + entry_address;
uint16_t node_l = xe::load<uint16_t>(data + 0);
uint16_t node_r = xe::load<uint16_t>(data + 2);
uint32_t data_block = xe::load<uint32_t>(data + 4);
uint32_t length = xe::load<uint32_t>(data + 8);
uint8_t attributes = xe::load<uint8_t>(data + 12);
uint8_t name_length = xe::load<uint8_t>(data + 13);
auto name = reinterpret_cast<const char*>(data + 14);
uint16_t node_l = xe::load<uint16_t>(data + 0x00);
uint16_t node_r = xe::load<uint16_t>(data + 0x02);
uint32_t data_block = xe::load<uint32_t>(data + 0x04);
uint32_t length = xe::load<uint32_t>(data + 0x08);
uint8_t attributes = xe::load<uint8_t>(data + 0x0C);
uint8_t name_length = xe::load<uint8_t>(data + 0x0D);
auto name = reinterpret_cast<const char*>(data + 0x0E);
auto name_str = std::string(name, name_length);
// Read the left node
if (node_l && !ReadEntrySVOD(block, node_l, parent)) {
return false;
if (node_l) {
auto node_result = ReadEntrySVOD(block, node_l, parent);
if (node_result != Error::kSuccess) {
return node_result;
}
}
// Read file & address of block's data
@ -264,20 +318,25 @@ bool StfsContainerDevice::ReadEntrySVOD(uint32_t block, uint32_t ordinal,
BlockToOffsetSVOD(data_block, &data_address, &data_file);
// Create the entry
auto name_str = std::string(name, name_length);
// NOTE: SVOD entries don't have timestamps for individual files, which can
// cause issues when decrypting games. Using the root entry's timestamp
// solves this issues.
auto entry = StfsContainerEntry::Create(this, parent, name_str, &mmap_);
if (attributes & kFileAttributeDirectory) {
// Entry is a folder
// Entry is a directory
entry->attributes_ = kFileAttributeDirectory | kFileAttributeReadOnly;
entry->data_offset_ = 0;
entry->data_size_ = 0;
entry->block_ = block;
entry->access_timestamp_ = root_entry_->create_timestamp();
entry->create_timestamp_ = root_entry_->create_timestamp();
entry->write_timestamp_ = root_entry_->create_timestamp();
if (length) {
// Folder contains children
if (!ReadEntrySVOD(data_block, 0, entry.get())) {
return false;
// If length is greater than 0, traverse the directory's children
auto directory_result = ReadEntrySVOD(data_block, 0, entry.get());
if (directory_result != Error::kSuccess) {
return directory_result;
}
}
} else {
@ -288,6 +347,9 @@ bool StfsContainerDevice::ReadEntrySVOD(uint32_t block, uint32_t ordinal,
entry->data_offset_ = data_address;
entry->data_size_ = length;
entry->block_ = data_block;
entry->access_timestamp_ = root_entry_->create_timestamp();
entry->create_timestamp_ = root_entry_->create_timestamp();
entry->write_timestamp_ = root_entry_->create_timestamp();
// Fill in all block records, sector by sector.
if (entry->attributes() & X_FILE_ATTRIBUTE_NORMAL) {
@ -321,16 +383,81 @@ bool StfsContainerDevice::ReadEntrySVOD(uint32_t block, uint32_t ordinal,
parent->children_.emplace_back(std::move(entry));
// Read next file in the list.
if (node_r && !ReadEntrySVOD(block, node_r, parent)) {
return false;
// Read the right node.
if (node_r) {
auto node_result = ReadEntrySVOD(block, node_r, parent);
if (node_result != Error::kSuccess) {
return node_result;
}
}
return true;
return Error::kSuccess;
}
StfsContainerDevice::Error StfsContainerDevice::ReadAllEntriesSTFS(
const uint8_t* map_ptr) {
void StfsContainerDevice::BlockToOffsetSVOD(size_t block, size_t* out_address,
size_t* out_file_index) {
// SVOD Systems use hash blocks for integrity checks. These hash blocks
// cause blocks to be discontinuous in memory, and must be accounted for.
// - Each data block is 0x800 bytes in length
// - Every group of 0x198 data blocks is preceded a Level0 hash table.
// Level0 tables contain 0xCC hashes, each representing two data blocks.
// The total size of each Level0 hash table is 0x1000 bytes in length.
// - Every 0xA1C4 Level0 hash tables is preceded by a Level1 hash table.
// Level1 tables contain 0xCB hashes, each representing two Level0 hashes.
// The total size of each Level1 hash table is 0x1000 bytes in length.
// - Files are split into fragments of 0xA290000 bytes in length,
// consisting of 0x14388 data blocks, 0xCB Level0 hash tables, and 0x1
// Level1 hash table.
const size_t BLOCK_SIZE = 0x800;
const size_t HASH_BLOCK_SIZE = 0x1000;
const size_t BLOCKS_PER_L0_HASH = 0x198;
const size_t HASHES_PER_L1_HASH = 0xA1C4;
const size_t BLOCKS_PER_FILE = 0x14388;
const size_t MAX_FILE_SIZE = 0xA290000;
const size_t BLOCK_OFFSET = header_.svod_volume_descriptor.data_block_offset;
const SvodLayoutType LAYOUT = header_.svod_volume_descriptor.layout_type;
// Resolve the true block address and file index
size_t true_block = block - (BLOCK_OFFSET * 2);
if (LAYOUT == kEnhancedGDFLayout) {
// EGDF has an 0x1000 byte offset, which is two blocks
true_block += 0x2;
}
size_t file_block = true_block % BLOCKS_PER_FILE;
size_t file_index = true_block / BLOCKS_PER_FILE;
size_t offset = 0;
// Calculate offset caused by Level0 Hash Tables
size_t level0_table_count = (file_block / BLOCKS_PER_L0_HASH) + 1;
offset += level0_table_count * HASH_BLOCK_SIZE;
// Calculate offset caused by Level1 Hash Tables
size_t level1_table_count = (level0_table_count / HASHES_PER_L1_HASH) + 1;
offset += level1_table_count * HASH_BLOCK_SIZE;
// For single-file SVOD layouts, include the size of the header in the offset.
if (LAYOUT == kSingleFileLayout) {
offset += base_offset_;
}
size_t block_address = (file_block * BLOCK_SIZE) + offset;
// If the offset causes the block address to overrun the file, round it.
if (block_address >= MAX_FILE_SIZE) {
file_index += 1;
block_address %= MAX_FILE_SIZE;
block_address += 0x2000;
}
*out_address = block_address;
*out_file_index = file_index;
}
StfsContainerDevice::Error StfsContainerDevice::ReadSTFS() {
auto data = mmap_.at(0)->data();
auto root_entry = new StfsContainerEntry(this, nullptr, "", &mmap_);
root_entry->attributes_ = kFileAttributeDirectory;
root_entry_ = std::unique_ptr<Entry>(root_entry);
@ -341,7 +468,7 @@ StfsContainerDevice::Error StfsContainerDevice::ReadAllEntriesSTFS(
auto& volume_descriptor = header_.stfs_volume_descriptor;
uint32_t table_block_index = volume_descriptor.file_table_block_number;
for (size_t n = 0; n < volume_descriptor.file_table_block_count; n++) {
const uint8_t* p = map_ptr + BlockToOffsetSTFS(table_block_index);
const uint8_t* p = data + BlockToOffsetSTFS(table_block_index);
for (size_t m = 0; m < 0x1000 / 0x40; m++) {
const uint8_t* filename = p; // 0x28b
if (filename[0] == 0) {
@ -405,9 +532,9 @@ StfsContainerDevice::Error StfsContainerDevice::ReadAllEntriesSTFS(
size_t offset = BlockToOffsetSTFS(block_index);
entry->block_list_.push_back({0, offset, block_size});
remaining_size -= block_size;
auto block_hash = GetBlockHash(map_ptr, block_index, 0);
auto block_hash = GetBlockHash(data, block_index, 0);
if (table_size_shift_ && block_hash.info < 0x80) {
block_hash = GetBlockHash(map_ptr, block_index, 1);
block_hash = GetBlockHash(data, block_index, 1);
}
block_index = block_hash.next_block_index;
info = block_hash.info;
@ -417,9 +544,9 @@ StfsContainerDevice::Error StfsContainerDevice::ReadAllEntriesSTFS(
parent_entry->children_.emplace_back(std::move(entry));
}
auto block_hash = GetBlockHash(map_ptr, table_block_index, 0);
auto block_hash = GetBlockHash(data, table_block_index, 0);
if (table_size_shift_ && block_hash.info < 0x80) {
block_hash = GetBlockHash(map_ptr, table_block_index, 1);
block_hash = GetBlockHash(data, table_block_index, 1);
}
table_block_index = block_hash.next_block_index;
if (table_block_index == 0xFFFFFF) {
@ -438,30 +565,20 @@ size_t StfsContainerDevice::BlockToOffsetSTFS(uint64_t block_index) {
block_shift = package_type_ == StfsPackageType::kCon ? 1 : 0;
}
if (header_.descriptor_type == StfsDescriptorType::kStfs) {
// For every level there is a hash table
// Level 0: hash table of next 170 blocks
// Level 1: hash table of next 170 hash tables
// Level 2: hash table of next 170 level 1 hash tables
// And so on...
uint64_t base = kSTFSHashSpacing;
block = block_index;
for (uint32_t i = 0; i < 3; i++) {
block += (block_index + (base << block_shift)) / (base << block_shift);
if (block_index < base) {
break;
}
base *= kSTFSHashSpacing;
// For every level there is a hash table
// Level 0: hash table of next 170 blocks
// Level 1: hash table of next 170 hash tables
// Level 2: hash table of next 170 level 1 hash tables
// And so on...
uint64_t base = kSTFSHashSpacing;
block = block_index;
for (uint32_t i = 0; i < 3; i++) {
block += (block_index + (base << block_shift)) / (base << block_shift);
if (block_index < base) {
break;
}
} else if (header_.descriptor_type == StfsDescriptorType::kSvod) {
// Level 0: Hashes for the next 204 blocks
// Level 1: Hashes for the next 203 hash blocks + 1 for the next level 0
// 10......[204 blocks].....0.....[204 blocks].....0
// There are 0xA1C4 (41412) blocks for every level 1 hash table.
block = block_index;
block += (block_index + 204) / 204; // Level 0
block += (block_index + 204 * 203) / (204 * 203); // Level 1
base *= kSTFSHashSpacing;
}
return xe::round_up(header_.header_size, 0x1000) + (block << 12);
@ -485,52 +602,6 @@ StfsContainerDevice::BlockHash StfsContainerDevice::GetBlockHash(
return {next_block_index, info};
}
void StfsContainerDevice::BlockToOffsetSVOD(size_t block, size_t* out_address,
size_t* out_file_index) {
/* Blocks are 0x800 bytes each */
/* Every 0x198 blocks there is a Level 0 hash table of size 0x1000,
which contains the hashes of the next 0x198 blocks. Hashes are 0x14 bytes
each, and there is 0x10 bytes of padding at the end. */
/* Every 0xA1C4 blocks there is a Level 1 hash table of size 0x1000,
which contains the hashes of the next 0xCB Level 0 hash blocks.
Hashes are 0x14 bytes each and there is 0x10 bytes of padding at
the end. */
/* Files are split up into chunks of 0xA290000 bytes. */
const size_t BLOCK_SIZE = 0x800;
const size_t HASH_BLOCK_SIZE = 0x1000;
const size_t BLOCKS_PER_L0_HASH = 0x198;
const size_t HASHES_PER_L1_HASH = 0xA1C4;
const size_t BLOCKS_PER_FILE = 0x14388;
const size_t MAX_FILE_SIZE = 0xA290000;
const size_t BLOCK_OFFSET = header_.svod_volume_descriptor.data_block_offset;
// Resolve the true block address and file index
size_t true_block = block - (BLOCK_OFFSET * 2);
size_t file_block = true_block % BLOCKS_PER_FILE;
size_t file_index = true_block / BLOCKS_PER_FILE;
size_t offset = 0;
// Calculate offset caused by Level0 Hash Tables
size_t level0_table_count = (file_block / BLOCKS_PER_L0_HASH) + 1;
offset += level0_table_count * HASH_BLOCK_SIZE;
// Calculate offset caused by Level1 Hash Tables
size_t level1_table_count = (level0_table_count / HASHES_PER_L1_HASH) + 1;
offset += level1_table_count * HASH_BLOCK_SIZE;
size_t block_address = (file_block * BLOCK_SIZE) + base_address_ + offset;
// If the offset causes the block address to overrun the file, round it
if (block_address >= MAX_FILE_SIZE) {
file_index += 1;
block_address %= block_address;
}
*out_address = block_address;
*out_file_index = file_index;
}
bool StfsVolumeDescriptor::Read(const uint8_t* p) {
descriptor_size = xe::load_and_swap<uint8_t>(p + 0x00);
if (descriptor_size != 0x24) {
@ -614,16 +685,18 @@ bool StfsHeader::Read(const uint8_t* p) {
std::memcpy(title_thumbnail_image, p + 0x571A, 0x4000);
// Metadata v2 Fields
std::memcpy(series_id, p + 0x3B1, 0x10);
std::memcpy(season_id, p + 0x3C1, 0x10);
season_number = xe::load_and_swap<uint16_t>(p + 0x3D1);
episode_number = xe::load_and_swap<uint16_t>(p + 0x3D5);
if (metadata_version == 2) {
std::memcpy(series_id, p + 0x3B1, 0x10);
std::memcpy(season_id, p + 0x3C1, 0x10);
season_number = xe::load_and_swap<uint16_t>(p + 0x3D1);
episode_number = xe::load_and_swap<uint16_t>(p + 0x3D5);
for (size_t n = 0; n < 0x300 / 2; n++) {
additonal_display_names[n] =
xe::load_and_swap<uint16_t>(p + 0x541A + n * 2);
additional_display_descriptions[n] =
xe::load_and_swap<uint16_t>(p + 0x941A + n * 2);
for (size_t n = 0; n < 0x300 / 2; n++) {
additonal_display_names[n] =
xe::load_and_swap<uint16_t>(p + 0x541A + n * 2);
additional_display_descriptions[n] =
xe::load_and_swap<uint16_t>(p + 0x941A + n * 2);
}
}
return true;
@ -662,7 +735,7 @@ bool StfsContainerDevice::ResolveFromFolder(const std::wstring& path) {
if (memcmp(magic, "LIVE", 4) == 0 || memcmp(magic, "PIRS", 4) == 0 ||
memcmp(magic, "CON ", 4) == 0) {
local_path_ = xe::join_paths(current_file.path, current_file.name);
XELOGI("STFS Package found: %s", local_path_.c_str());
XELOGI("STFS Package found: %s", xe::to_string(local_path_).c_str());
return true;
}
}

View File

@ -92,6 +92,13 @@ enum SvodDeviceFeatures {
kFeatureHasEnhancedGDFLayout = 0x40,
};
enum SvodLayoutType {
kUnknownLayout = 0x0,
kEnhancedGDFLayout = 0x1,
kXSFLayout = 0x2,
kSingleFileLayout = 0x4,
};
struct SvodVolumeDescriptor {
bool Read(const uint8_t* p);
@ -104,6 +111,8 @@ struct SvodVolumeDescriptor {
uint32_t data_block_count;
uint32_t data_block_offset;
// 0x5 padding bytes...
SvodLayoutType layout_type;
};
class StfsHeader {
@ -187,20 +196,21 @@ class StfsContainerDevice : public Device {
};
const uint32_t kSTFSHashSpacing = 170;
const uint32_t kSVODHashSpacing = 204;
const char* ReadMagic(const std::wstring& path);
bool ResolveFromFolder(const std::wstring& path);
Error MapFiles();
Error ReadHeaderAndVerify(const uint8_t* map_ptr);
Error ReadAllEntriesSVOD();
bool ReadEntrySVOD(uint32_t sector, uint32_t ordinal,
StfsContainerEntry* parent);
Error ReadAllEntriesSTFS(const uint8_t* map_ptr);
size_t BlockToOffsetSTFS(uint64_t block);
Error ReadSVOD();
Error ReadEntrySVOD(uint32_t sector, uint32_t ordinal,
StfsContainerEntry* parent);
void BlockToOffsetSVOD(size_t sector, size_t* address, size_t* file_index);
Error ReadSTFS();
size_t BlockToOffsetSTFS(uint64_t block);
BlockHash GetBlockHash(const uint8_t* map_ptr, uint32_t block_index,
uint32_t table_offset);
@ -208,8 +218,8 @@ class StfsContainerDevice : public Device {
std::map<size_t, std::unique_ptr<MappedMemory>> mmap_;
size_t mmap_total_size_;
size_t base_address_;
size_t base_offset_;
size_t magic_offset_;
std::unique_ptr<Entry> root_entry_;
StfsPackageType package_type_;
StfsHeader header_;