From 151a955c6afa13d55778c1a0e72ff55d28e19922 Mon Sep 17 00:00:00 2001 From: Wildhaus Date: Tue, 27 Nov 2018 16:19:51 -0500 Subject: [PATCH] Support EGDF and XSF layouts --- .../vfs/devices/stfs_container_device.cc | 465 ++++++++++-------- src/xenia/vfs/devices/stfs_container_device.h | 26 +- 2 files changed, 287 insertions(+), 204 deletions(-) diff --git a/src/xenia/vfs/devices/stfs_container_device.cc b/src/xenia/vfs/devices/stfs_container_device.cc index ea8d1dde7..a04215a3e 100644 --- a/src/xenia/vfs/devices/stfs_container_device.cc +++ b/src/xenia/vfs/devices/stfs_container_device.cc @@ -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(p + 0x14); - uint32_t root_size = xe::load(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(magic_block + 0x14); + uint32_t root_size = xe::load(magic_block + 0x18); + uint32_t root_creation_date = xe::load(magic_block + 0x1C); + uint32_t root_creation_time = xe::load(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(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(data + 0); - uint16_t node_r = xe::load(data + 2); - uint32_t data_block = xe::load(data + 4); - uint32_t length = xe::load(data + 8); - uint8_t attributes = xe::load(data + 12); - uint8_t name_length = xe::load(data + 13); - auto name = reinterpret_cast(data + 14); + uint16_t node_l = xe::load(data + 0x00); + uint16_t node_r = xe::load(data + 0x02); + uint32_t data_block = xe::load(data + 0x04); + uint32_t length = xe::load(data + 0x08); + uint8_t attributes = xe::load(data + 0x0C); + uint8_t name_length = xe::load(data + 0x0D); + auto name = reinterpret_cast(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(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(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(p + 0x3D1); - episode_number = xe::load_and_swap(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(p + 0x3D1); + episode_number = xe::load_and_swap(p + 0x3D5); - for (size_t n = 0; n < 0x300 / 2; n++) { - additonal_display_names[n] = - xe::load_and_swap(p + 0x541A + n * 2); - additional_display_descriptions[n] = - xe::load_and_swap(p + 0x941A + n * 2); + for (size_t n = 0; n < 0x300 / 2; n++) { + additonal_display_names[n] = + xe::load_and_swap(p + 0x541A + n * 2); + additional_display_descriptions[n] = + xe::load_and_swap(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; } } diff --git a/src/xenia/vfs/devices/stfs_container_device.h b/src/xenia/vfs/devices/stfs_container_device.h index c34632ce1..495e0f8d1 100644 --- a/src/xenia/vfs/devices/stfs_container_device.h +++ b/src/xenia/vfs/devices/stfs_container_device.h @@ -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> mmap_; size_t mmap_total_size_; - size_t base_address_; - + size_t base_offset_; + size_t magic_offset_; std::unique_ptr root_entry_; StfsPackageType package_type_; StfsHeader header_;