diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index 4aae8d1a5..f567ed502 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -124,7 +124,7 @@ X_STATUS UserModule::LoadFromFile(std::string path) { content_manager->SetTitleIdOverride(exec_info->title_id); auto update_packages = content_manager->ListContent( - 0, (uint32_t)vfs::StfsContentType::kInstaller); + 0, (uint32_t)vfs::XContentType::kInstaller); for (auto& update : update_packages) { auto result = content_manager->OpenContent("update", update); diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index 11d3474b6..d43e54112 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -80,9 +80,6 @@ std::vector ContentManager::ListContent(uint32_t device_id, uint32_t content_type) { std::vector result; - // StfsHeader is a huge class - alloc on heap instead of stack - vfs::StfsHeader* header = new vfs::StfsHeader(); - // Search path: // content_root/title_id/type_name/* auto package_root = ResolvePackageRoot(content_type); @@ -104,21 +101,20 @@ std::vector ContentManager::ListContent(uint32_t device_id, if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { // Not a directory so must be a package, verify size to make sure - if (file_info.total_size <= vfs::StfsHeader::kHeaderLength) { + if (file_info.total_size <= sizeof(vfs::StfsHeader)) { continue; // Invalid package (maybe .headers.bin) } } auto map = MappedMemory::Open(headers_path, MappedMemory::Mode::kRead, 0, - vfs::StfsHeader::kHeaderLength); + sizeof(vfs::StfsHeader)); if (map) { - if (header->Read(map->data())) { - content_data.content_type = - static_cast(header->content_type); - content_data.display_name = header->display_names; - // TODO: select localized display name - // some games may expect different ones depending on language setting. - } + auto* header = (vfs::StfsHeader*)map->data(); + content_data.content_type = + static_cast(header->metadata.content_type); + content_data.display_name = header->metadata.display_name[0]; + // TODO: select localized display name + // some games may expect different ones depending on language setting. map->Close(); } } @@ -126,8 +122,6 @@ std::vector ContentManager::ListContent(uint32_t device_id, result.emplace_back(std::move(content_data)); } - delete header; - return result; } diff --git a/src/xenia/kernel/xam/content_package.cc b/src/xenia/kernel/xam/content_package.cc index 3fa04efd9..201dbceb1 100644 --- a/src/xenia/kernel/xam/content_package.cc +++ b/src/xenia/kernel/xam/content_package.cc @@ -114,19 +114,15 @@ X_RESULT FolderContentPackage::GetThumbnail(std::vector* buffer) { // Try reading thumbnail from kStfsHeadersExtension file auto headers_path = package_path_ + ContentManager::kStfsHeadersExtension; if (xe::filesystem::PathExists(headers_path)) { - vfs::StfsHeader* header = - new vfs::StfsHeader(); // huge class, alloc on heap auto map = MappedMemory::Open(headers_path, MappedMemory::Mode::kRead, 0, - vfs::StfsHeader::kHeaderLength); + sizeof(vfs::StfsHeader)); if (map) { - if (header->Read(map->data())) { - buffer->resize(header->thumbnail_image_size); - memcpy(buffer->data(), header->thumbnail_image, - header->thumbnail_image_size); - result = X_ERROR_SUCCESS; - } + auto* header = (vfs::StfsHeader*)map->data(); + buffer->resize(header->metadata.thumbnail_size); + memcpy(buffer->data(), header->metadata.thumbnail, + header->metadata.thumbnail_size); + result = X_ERROR_SUCCESS; } - delete header; } return result; } @@ -175,8 +171,9 @@ X_RESULT StfsContentPackage::GetThumbnail(std::vector* buffer) { if (!device_inited_) { return X_ERROR_DEVICE_NOT_CONNECTED; } - buffer->resize(header_.thumbnail_image_size); - memcpy(buffer->data(), header_.thumbnail_image, header_.thumbnail_image_size); + buffer->resize(header_.metadata.thumbnail_size); + memcpy(buffer->data(), header_.metadata.thumbnail, + header_.metadata.thumbnail_size); return X_ERROR_SUCCESS; } diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index ff258acd9..e8d2a6a14 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -524,7 +524,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { std::vector spa_achievements; // TODO: let user choose locale? - spa_data.GetAchievements(spa_data.GetDefaultLocale(), &spa_achievements); + spa_data.GetAchievements(spa_data.GetDefaultLanguage(), &spa_achievements); // Check if title should be included in the dash GPD title list // These checks should hopefully match the same checks X360 uses diff --git a/src/xenia/kernel/xam/xdbf/xdbf.cc b/src/xenia/kernel/xam/xdbf/xdbf.cc index 89412b5b3..cac9ec5cb 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.cc +++ b/src/xenia/kernel/xam/xdbf/xdbf.cc @@ -162,10 +162,10 @@ std::string GetStringTableEntry_(const uint8_t* table_start, uint16_t string_id, return ""; } -std::string SpaFile::GetStringTableEntry(Locale locale, +std::string SpaFile::GetStringTableEntry(XLanguage language, uint16_t string_id) const { auto xstr_table = GetEntry(static_cast(SpaSection::kStringTable), - static_cast(locale)); + static_cast(language)); if (!xstr_table) { return ""; } @@ -181,7 +181,7 @@ std::string SpaFile::GetStringTableEntry(Locale locale, } uint32_t SpaFile::GetAchievements( - Locale locale, std::vector* achievements) const { + XLanguage lang, std::vector* achievements) const { auto xach_table = GetEntry(static_cast(SpaSection::kMetadata), static_cast(SpaID::Xach)); if (!xach_table) { @@ -194,7 +194,7 @@ uint32_t SpaFile::GetAchievements( assert_true(xach_head->header.version == 1); auto xstr_table = GetEntry(static_cast(SpaSection::kStringTable), - static_cast(locale)); + static_cast(lang)); if (!xstr_table) { return 0; } @@ -240,21 +240,21 @@ Entry* SpaFile::GetIcon() const { static_cast(SpaID::Title)); } -Locale SpaFile::GetDefaultLocale() const { +XLanguage SpaFile::GetDefaultLanguage() const { auto block = GetEntry(static_cast(SpaSection::kMetadata), static_cast(SpaID::Xstc)); if (!block) { - return Locale::kEnglish; + return XLanguage::kEnglish; } auto xstc = reinterpret_cast(block->data.data()); assert_true(xstc->header.magic == static_cast(SpaID::Xstc)); - return static_cast(static_cast(xstc->default_language)); + return static_cast(static_cast(xstc->default_language)); } std::string SpaFile::GetTitleName() const { - return GetStringTableEntry(GetDefaultLocale(), + return GetStringTableEntry(GetDefaultLanguage(), static_cast(SpaID::Title)); } diff --git a/src/xenia/kernel/xam/xdbf/xdbf.h b/src/xenia/kernel/xam/xdbf/xdbf.h index 5edb7a3fd..f654e1756 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.h +++ b/src/xenia/kernel/xam/xdbf/xdbf.h @@ -15,8 +15,8 @@ #include "xenia/base/clock.h" #include "xenia/base/memory.h" - #include "xenia/kernel/xam/xdbf/xdbf_xbox.h" +#include "xenia/xbox.h" namespace xe { namespace kernel { @@ -49,19 +49,6 @@ enum class GpdSection : uint16_t { kProtectedAchievement = 0x6, // GFWL only }; -// Found by dumping the kSectionStringTable sections of various games: -enum class Locale : uint32_t { - kUnknown = 0, - kEnglish = 1, - kJapanese = 2, - kGerman = 3, - kFrench = 4, - kSpanish = 5, - kItalian = 6, - kKorean = 7, - kChinese = 8, -}; - inline std::wstring ReadNullTermString(const wchar_t* ptr) { std::wstring retval; wchar_t data = xe::byte_swap(*ptr); @@ -351,13 +338,13 @@ class XdbfFile { class SpaFile : public XdbfFile { public: - std::string GetStringTableEntry(Locale locale, uint16_t string_id) const; + std::string GetStringTableEntry(XLanguage lang, uint16_t string_id) const; - uint32_t GetAchievements(Locale locale, + uint32_t GetAchievements(XLanguage lang, std::vector* achievements) const; Entry* GetIcon() const; - Locale GetDefaultLocale() const; + XLanguage GetDefaultLanguage() const; std::string GetTitleName() const; bool GetTitleData(X_XDBF_XTHD_DATA* title_data) const; }; diff --git a/src/xenia/vfs/devices/stfs_container_device.cc b/src/xenia/vfs/devices/stfs_container_device.cc index e0470047e..3c21fcc62 100644 --- a/src/xenia/vfs/devices/stfs_container_device.cc +++ b/src/xenia/vfs/devices/stfs_container_device.cc @@ -79,14 +79,14 @@ bool StfsContainerDevice::Initialize() { return false; } - switch (header_.descriptor_type) { - case StfsDescriptorType::kStfs: + switch (header_.metadata.volume_type) { + case XContentVolumeType::kStfs: return ReadSTFS() == Error::kSuccess; break; - case StfsDescriptorType::kSvod: + case XContentVolumeType::kSvod: return ReadSVOD() == Error::kSuccess; default: - XELOGE("Unknown STFS Descriptor Type: %d", header_.descriptor_type); + XELOGE("Unknown XContent volume type: %d", header_.metadata.volume_type); return false; } } @@ -110,7 +110,7 @@ StfsContainerDevice::Error StfsContainerDevice::MapFiles() { // 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) { + if (header_.metadata.data_file_count <= 1) { XELOGI("STFS container is a single file."); mmap_.emplace(std::make_pair(0, std::move(header_map))); return Error::kSuccess; @@ -132,9 +132,9 @@ StfsContainerDevice::Error StfsContainerDevice::MapFiles() { return left.name < right.name; }); - if (fragment_files.size() != header_.data_file_count) { + if (fragment_files.size() != header_.metadata.data_file_count) { XELOGE("SVOD expecting %d data fragments, but %d are present.", - header_.data_file_count, fragment_files.size()); + header_.metadata.data_file_count, fragment_files.size()); return Error::kErrorFileMismatch; } @@ -179,45 +179,15 @@ Entry* StfsContainerDevice::ResolvePath(const std::string& path) { return entry; } -StfsContainerDevice::Error StfsContainerDevice::ReadPackageType( - const uint8_t* map_ptr, size_t map_size, - StfsPackageType* package_type_out) { - if (map_size < 4) { - return Error::kErrorFileMismatch; - } - if (memcmp(map_ptr, "LIVE", 4) == 0) { - if (package_type_out) { - *package_type_out = StfsPackageType::kLive; - } - return Error::kSuccess; - } - if (memcmp(map_ptr, "PIRS", 4) == 0) { - if (package_type_out) { - *package_type_out = StfsPackageType::kPirs; - } - return Error::kSuccess; - } - if (memcmp(map_ptr, "CON ", 4) == 0) { - if (package_type_out) { - *package_type_out = StfsPackageType::kCon; - } - return Error::kSuccess; - } - // Unexpected format. - return Error::kErrorFileMismatch; -} - StfsContainerDevice::Error StfsContainerDevice::ReadHeaderAndVerify( const uint8_t* map_ptr, size_t map_size) { - // Check signature. - auto type_result = ReadPackageType(map_ptr, map_size, &package_type_); - if (type_result != Error::kSuccess) { - return type_result; - } - - // Read header. - if (!header_.Read(map_ptr)) { - return Error::kErrorDamagedFile; + // Copy header & check signature + memcpy(&header_, map_ptr, sizeof(StfsHeader)); + if (header_.header.magic != XContentPackageType::kPackageTypeCon && + header_.header.magic != XContentPackageType::kPackageTypeLive && + header_.header.magic != XContentPackageType::kPackageTypePirs) { + // Unexpected format. + return Error::kErrorFileMismatch; } // Pre-calculate some values used in block number calculations @@ -231,7 +201,7 @@ StfsContainerDevice::Error StfsContainerDevice::ReadHeaderAndVerify( // single block for each table, right? // Need to verify with kernel if it actually bases anything on the header_size // field. - if (!header_.stfs_volume_descriptor.read_only_package()) { + if (!header_.metadata.stfs_volume_descriptor.flags.read_only_format) { blocks_per_hash_table_ = 2; block_step_[0] = 0xAC; block_step_[1] = 0x723A; @@ -248,9 +218,8 @@ StfsContainerDevice::Error StfsContainerDevice::ReadSVOD() { 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; + bool has_egdf_layout = + header_.metadata.svod_volume_descriptor.features.enhanced_gdf_layout; if (has_egdf_layout) { // The STFS header has specified that this SVOD system uses the EGDF layout. @@ -260,7 +229,7 @@ StfsContainerDevice::Error StfsContainerDevice::ReadSVOD() { if (memcmp(data + 0x2000, MEDIA_MAGIC, 20) == 0) { base_offset_ = 0x0000; magic_offset_ = 0x2000; - *layout = kEnhancedGDFLayout; + svod_layout_ = SvodLayoutType::kEnhancedGDF; 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."); @@ -277,11 +246,11 @@ StfsContainerDevice::Error StfsContainerDevice::ReadSVOD() { // Check for XSF Header const char* XSF_MAGIC = "XSF"; if (memcmp(data + 0x2000, XSF_MAGIC, 3) == 0) { - *layout = kXSFLayout; + svod_layout_ = SvodLayoutType::kXSF; XELOGI("SVOD uses an XSF layout. Magic block present at 0x12000."); XELOGI("Game was likely converted using a third-party tool."); } else { - *layout = kUnknownLayout; + svod_layout_ = SvodLayoutType::kUnknown; XELOGI("SVOD appears to use an XSF layout, but no header is present."); XELOGI("SVOD magic block found at 0x12000"); } @@ -294,11 +263,11 @@ StfsContainerDevice::Error StfsContainerDevice::ReadSVOD() { magic_offset_ = 0xD000; // Check for single file system - if (header_.data_file_count == 1) { - *layout = kSingleFileLayout; + if (header_.metadata.data_file_count == 1) { + svod_layout_ = SvodLayoutType::kSingleFile; XELOGI("SVOD is a single file. Magic block present at 0xD000."); } else { - *layout = kUnknownLayout; + svod_layout_ = SvodLayoutType::kUnknown; XELOGE( "SVOD is not a single file, but the magic block was found at " "0xD000."); @@ -463,12 +432,12 @@ void StfsContainerDevice::BlockToOffsetSVOD(size_t block, size_t* out_address, 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; + const size_t BLOCK_OFFSET = + header_.metadata.svod_volume_descriptor.start_data_block(); // Resolve the true block address and file index size_t true_block = block - (BLOCK_OFFSET * 2); - if (LAYOUT == kEnhancedGDFLayout) { + if (svod_layout_ == SvodLayoutType::kEnhancedGDF) { // EGDF has an 0x1000 byte offset, which is two blocks true_block += 0x2; } @@ -486,7 +455,7 @@ void StfsContainerDevice::BlockToOffsetSVOD(size_t block, size_t* out_address, offset += level1_table_count * HASH_BLOCK_SIZE; // For single-file SVOD layouts, include the size of the header in the offset. - if (LAYOUT == kSingleFileLayout) { + if (svod_layout_ == SvodLayoutType::kSingleFile) { offset += base_offset_; } @@ -513,9 +482,9 @@ StfsContainerDevice::Error StfsContainerDevice::ReadSTFS() { std::vector all_entries; // Load all listings. - 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++) { + auto& volume_descriptor = header_.metadata.stfs_volume_descriptor; + uint32_t table_block_index = volume_descriptor.directory_block_num(); + for (size_t n = 0; n < volume_descriptor.directory_block_count(); n++) { const uint8_t* p = data + STFSDataBlockToOffset(table_block_index); for (size_t m = 0; m < 0x1000 / 0x40; m++) { const uint8_t* filename = p; // 0x28b @@ -651,7 +620,8 @@ uint64_t StfsContainerDevice::STFSDataBlockToBackingHashBlock(uint64_t block, } size_t StfsContainerDevice::STFSBackingBlockToOffset(uint64_t backing_block) { - return xe::round_up(header_.header_size, 0x1000) + (backing_block * 0x1000); + return xe::round_up(header_.header.header_size, 0x1000) + + (backing_block * 0x1000); } size_t StfsContainerDevice::STFSDataBlockToOffset(uint64_t block) { @@ -675,7 +645,8 @@ StfsContainerDevice::BlockHash StfsContainerDevice::STFSGetLevelNHashEntry( size_t hash_offset = STFSDataBlockToBackingHashBlockOffset(block_index, level); - if (secondary_block && !header_.stfs_volume_descriptor.read_only_package()) { + if (secondary_block && + !header_.metadata.stfs_volume_descriptor.flags.read_only_format) { hash_offset += bytes_per_sector(); // read from this tables secondary block } @@ -691,7 +662,7 @@ StfsContainerDevice::BlockHash StfsContainerDevice::STFSGetLevel0HashEntry( const uint8_t* map_ptr, uint32_t block_index) { bool use_secondary_block = false; // Use secondary block for root table if RootActiveIndex flag is set - if (header_.stfs_volume_descriptor.root_table_secondary()) { + if (header_.metadata.stfs_volume_descriptor.flags.root_active_index) { use_secondary_block = true; } @@ -699,9 +670,9 @@ StfsContainerDevice::BlockHash StfsContainerDevice::STFSGetLevel0HashEntry( // use. // We should be able to skip this if it's a read-only package, since the hash // tables in those only use one block - if (!header_.stfs_volume_descriptor.read_only_package()) { + if (!header_.metadata.stfs_volume_descriptor.flags.read_only_format) { auto num_blocks = - header_.stfs_volume_descriptor.total_allocated_block_count; + header_.metadata.stfs_volume_descriptor.allocated_block_count; if (num_blocks >= kSTFSDataBlocksPerHashLevel[1]) { // Get the L2 entry for the block @@ -727,104 +698,11 @@ StfsContainerDevice::BlockHash StfsContainerDevice::STFSGetLevel0HashEntry( return STFSGetLevelNHashEntry(map_ptr, block_index, 0, use_secondary_block); } -bool StfsVolumeDescriptor::Read(const uint8_t* p) { - descriptor_size = xe::load_and_swap(p + 0x00); - if (descriptor_size != 0x24) { - XELOGE("STFS volume descriptor size mismatch, expected 0x24 but got 0x%X", - descriptor_size); - return false; - } - version = xe::load_and_swap(p + 0x01); - flags = xe::load_and_swap(p + 0x02); - file_table_block_count = xe::load(p + 0x03); - file_table_block_number = load_uint24_le(p + 0x05); - std::memcpy(top_hash_table_hash, p + 0x08, 0x14); - total_allocated_block_count = xe::load_and_swap(p + 0x1C); - total_unallocated_block_count = xe::load_and_swap(p + 0x20); - return true; -} - -bool SvodVolumeDescriptor::Read(const uint8_t* p) { - descriptor_size = xe::load(p + 0x00); - if (descriptor_size != 0x24) { - XELOGE("SVOD volume descriptor size mismatch, expected 0x24 but got 0x%X", - descriptor_size); - return false; - } - - block_cache_element_count = xe::load(p + 0x01); - worker_thread_processor = xe::load(p + 0x02); - worker_thread_priority = xe::load(p + 0x03); - std::memcpy(hash, p + 0x04, 0x14); - device_features = xe::load(p + 0x18); - data_block_count = load_uint24_be(p + 0x19); - data_block_offset = load_uint24_le(p + 0x1C); - return true; -} - -bool StfsHeader::Read(const uint8_t* p) { - std::memcpy(license_entries, p + 0x22C, 0x100); - std::memcpy(header_hash, p + 0x32C, 0x14); - header_size = xe::load_and_swap(p + 0x340); - content_type = (StfsContentType)xe::load_and_swap(p + 0x344); - metadata_version = xe::load_and_swap(p + 0x348); - content_size = xe::load_and_swap(p + 0x34C); - media_id = xe::load_and_swap(p + 0x354); - version = xe::load_and_swap(p + 0x358); - base_version = xe::load_and_swap(p + 0x35C); - title_id = xe::load_and_swap(p + 0x360); - platform = (StfsPlatform)xe::load_and_swap(p + 0x364); - executable_type = xe::load_and_swap(p + 0x365); - disc_number = xe::load_and_swap(p + 0x366); - disc_in_set = xe::load_and_swap(p + 0x367); - save_game_id = xe::load_and_swap(p + 0x368); - std::memcpy(console_id, p + 0x36C, 0x5); - std::memcpy(profile_id, p + 0x371, 0x8); - data_file_count = xe::load_and_swap(p + 0x39D); - data_file_combined_size = xe::load_and_swap(p + 0x3A1); - descriptor_type = (StfsDescriptorType)xe::load_and_swap(p + 0x3A9); - switch (descriptor_type) { - case StfsDescriptorType::kStfs: - stfs_volume_descriptor.Read(p + 0x379); - break; - case StfsDescriptorType::kSvod: - svod_volume_descriptor.Read(p + 0x379); - break; - default: - XELOGE("STFS descriptor format not supported: %d", descriptor_type); - return false; - } - memcpy(device_id, p + 0x3FD, 0x14); - for (size_t n = 0; n < 0x900 / 2; n++) { - display_names[n] = xe::load_and_swap(p + 0x411 + n * 2); - display_descs[n] = xe::load_and_swap(p + 0xD11 + n * 2); - } - for (size_t n = 0; n < 0x80 / 2; n++) { - publisher_name[n] = xe::load_and_swap(p + 0x1611 + n * 2); - title_name[n] = xe::load_and_swap(p + 0x1691 + n * 2); - } - transfer_flags = xe::load_and_swap(p + 0x1711); - thumbnail_image_size = xe::load_and_swap(p + 0x1712); - title_thumbnail_image_size = xe::load_and_swap(p + 0x1716); - std::memcpy(thumbnail_image, p + 0x171A, 0x4000); - std::memcpy(title_thumbnail_image, p + 0x571A, 0x4000); - - // Metadata v2 Fields - 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); - } - } - - return true; +const char* StfsContainerDevice::ReadMagic(const std::wstring& path) { + auto map = MappedMemory::Open(path, MappedMemory::Mode::kRead, 0, 4); + auto magic_data = xe::load(map->data()); + auto magic_bytes = static_cast(static_cast(&magic_data)); + return std::move(magic_bytes); } bool StfsContainerDevice::ResolveFromFolder(const std::wstring& path) { @@ -848,11 +726,12 @@ bool StfsContainerDevice::ResolveFromFolder(const std::wstring& path) { } else { // Try to read the file's magic auto path = xe::join_paths(current_file.path, current_file.name); - auto map = MappedMemory::Open(path, MappedMemory::Mode::kRead, 0, 4); - if (map && ReadPackageType(map->data(), map->size(), nullptr) == - Error::kSuccess) { + auto magic = ReadMagic(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: %ls", 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 92e45194c..6823bc91b 100644 --- a/src/xenia/vfs/devices/stfs_container_device.h +++ b/src/xenia/vfs/devices/stfs_container_device.h @@ -15,6 +15,7 @@ #include #include "xenia/base/mapped_memory.h" +#include "xenia/kernel/util/xex2_info.h" #include "xenia/vfs/device.h" namespace xe { @@ -24,156 +25,303 @@ namespace vfs { class StfsContainerEntry; -enum class StfsPackageType { - kCon, - kPirs, - kLive, -}; +/* STFS */ +struct StfsVolumeDescriptor { + uint8_t descriptor_length; + uint8_t version; + union { + struct { + uint8_t read_only_format : 1; + uint8_t root_active_index : 1; + uint8_t directory_overallocated : 1; + uint8_t directory_index_bounds_valid : 1; + }; + uint8_t as_byte; + } flags; + uint8_t directory_block_count0; + uint8_t directory_block_count1; + uint8_t directory_block_num0; + uint8_t directory_block_num1; + uint8_t directory_block_num2; + uint8_t root_hash[0x14]; + xe::be allocated_block_count; + xe::be free_block_count; -enum class StfsContentType : uint32_t { - kArcadeTitle = 0x000D0000, - kAvatarItem = 0x00009000, - kCacheFile = 0x00040000, - kCommunityGame = 0x02000000, - kGamesOnDemand = 0x00007000, - kGameDemo = 0x00080000, - kGamerPicture = 0x00020000, - kGameTitle = 0x000A0000, - kGameTrailer = 0x000C0000, - kGameVideo = 0x00400000, - kInstalledGame = 0x00004000, - kInstaller = 0x000B0000, - kIptvPauseBuffer = 0x00002000, - kLicenseStore = 0x000F0000, - kMarketplaceContent = 0x00000002, - kMovie = 0x00100000, - kMusicVideo = 0x00300000, - kPodcastVideo = 0x00500000, - kProfile = 0x00010000, - kPublisher = 0x00000003, + uint32_t directory_block_count() { + return directory_block_count0 | (directory_block_count1 << 8); + } + + uint32_t directory_block_num() { + return directory_block_num0 | (directory_block_num1 << 8) | + (directory_block_num2 << 16); + } +}; +static_assert_size(StfsVolumeDescriptor, 0x24); + +/* SVOD */ +struct SvodDeviceDescriptor { + uint8_t descriptor_length; + uint8_t block_cache_element_count; + uint8_t worker_thread_processor; + uint8_t worker_thread_priority; + uint8_t first_fragment_hash_entry[0x14]; + union { + struct { + uint8_t must_be_zero_for_future_usage : 6; + uint8_t enhanced_gdf_layout : 1; + uint8_t zero_for_downlevel_clients : 1; + }; + uint8_t as_byte; + } features; + uint8_t num_data_blocks2; + uint8_t num_data_blocks1; + uint8_t num_data_blocks0; + uint8_t start_data_block0; + uint8_t start_data_block1; + uint8_t start_data_block2; + uint8_t reserved[5]; + + uint32_t num_data_blocks() { + return num_data_blocks0 | (num_data_blocks1 << 8) | + (num_data_blocks2 << 16); + } + + uint32_t start_data_block() { + return start_data_block0 | (start_data_block1 << 8) | + (start_data_block2 << 16); + } +}; +static_assert_size(SvodDeviceDescriptor, 0x24); + +/* XContent */ +struct XContentMediaData { + uint8_t series_id[0x10]; + uint8_t season_id[0x10]; + xe::be season_number; + xe::be episode_number; +}; +static_assert_size(XContentMediaData, 0x24); + +#pragma pack(push, 1) +struct XContentAvatarAssetData { + xe::be sub_category; + xe::be colorizable; + uint8_t asset_id[0x10]; + uint8_t skeleton_version_mask; + uint8_t reserved[0xB]; +}; +static_assert_size(XContentAvatarAssetData, 0x24); + +struct XContentAttributes { + uint8_t profile_transfer : 1; + uint8_t device_transfer : 1; + uint8_t move_only_transfer : 1; + uint8_t kinect_enabled : 1; + uint8_t disable_network_storage : 1; + uint8_t deep_link_supported : 1; + uint8_t reserved : 2; +}; +static_assert_size(XContentAttributes, 1); + +enum XContentType : uint32_t { kSavedGame = 0x00000001, - kStorageDownload = 0x00050000, - kTheme = 0x00030000, - kTV = 0x00200000, - kVideo = 0x00090000, - kViralVideo = 0x00600000, - kXboxDownload = 0x00070000, - kXboxOriginalGame = 0x00005000, - kXboxSavedGame = 0x00060000, + kMarketplaceContent = 0x00000002, + kPublisher = 0x00000003, kXbox360Title = 0x00001000, + kIptvPauseBuffer = 0x00002000, + kXNACommunity = 0x00003000, + kInstalledGame = 0x00004000, kXboxTitle = 0x00005000, + kSocialTitle = 0x00006000, + kGamesOnDemand = 0x00007000, + kSUStoragePack = 0x00008000, + kAvatarItem = 0x00009000, + kProfile = 0x00010000, + kGamerPicture = 0x00020000, + kTheme = 0x00030000, + kCacheFile = 0x00040000, + kStorageDownload = 0x00050000, + kXboxSavedGame = 0x00060000, + kXboxDownload = 0x00070000, + kGameDemo = 0x00080000, + kVideo = 0x00090000, + kGameTitle = 0x000A0000, + kInstaller = 0x000B0000, + kGameTrailer = 0x000C0000, + kArcadeTitle = 0x000D0000, kXNA = 0x000E0000, + kLicenseStore = 0x000F0000, + kMovie = 0x00100000, + kTV = 0x00200000, + kMusicVideo = 0x00300000, + kGameVideo = 0x00400000, + kPodcastVideo = 0x00500000, + kViralVideo = 0x00600000, + kCommunityGame = 0x02000000, }; -enum class StfsPlatform : uint8_t { - kXbox360 = 0x02, - kPc = 0x04, -}; - -enum class StfsDescriptorType : uint32_t { +enum class XContentVolumeType : uint32_t { kStfs = 0, kSvod = 1, }; -struct StfsVolumeDescriptor { - bool Read(const uint8_t* p); - - uint8_t descriptor_size; - uint8_t version; - uint8_t flags; - uint16_t file_table_block_count; - uint32_t file_table_block_number; - uint8_t top_hash_table_hash[0x14]; - uint32_t total_allocated_block_count; - uint32_t total_unallocated_block_count; - - // Whether this is a read-only package - these will only use a single block - // for each hash table, compared to the two blocks used in non-read-only - bool read_only_package() { return (flags & 1) != 0; } - - // Whether the root hash table is stored in the hash tables secondary block - // Only valid if read_only_package is false - bool root_table_secondary() { - return !read_only_package() && (flags & 2) != 0; - } -}; - -enum SvodDeviceFeatures { - kFeatureHasEnhancedGDFLayout = 0x40, -}; - -enum SvodLayoutType { - kUnknownLayout = 0x0, - kEnhancedGDFLayout = 0x1, - kXSFLayout = 0x2, - kSingleFileLayout = 0x4, -}; - -struct SvodVolumeDescriptor { - bool Read(const uint8_t* p); - - uint8_t descriptor_size; - uint8_t block_cache_element_count; - uint8_t worker_thread_processor; - uint8_t worker_thread_priority; - uint8_t hash[0x14]; - uint8_t device_features; - uint32_t data_block_count; - uint32_t data_block_offset; - // 0x5 padding bytes... - - SvodLayoutType layout_type; -}; - -class StfsHeader { - public: - static const uint32_t kHeaderLength = 0xA000; - - bool Read(const uint8_t* p); - - uint8_t license_entries[0x100]; - uint8_t header_hash[0x14]; - uint32_t header_size; - StfsContentType content_type; - uint32_t metadata_version; - uint64_t content_size; - uint32_t media_id; - uint32_t version; - uint32_t base_version; - uint32_t title_id; - StfsPlatform platform; - uint8_t executable_type; - uint8_t disc_number; - uint8_t disc_in_set; - uint32_t save_game_id; - uint8_t console_id[0x5]; - uint8_t profile_id[0x8]; +struct XContentMetadata { + xe::be content_type; + xe::be metadata_version; + xe::be content_size; + xex2_opt_execution_info execution_info; + uint8_t console_id[5]; + xe::be profile_id; union { StfsVolumeDescriptor stfs_volume_descriptor; - SvodVolumeDescriptor svod_volume_descriptor; + SvodDeviceDescriptor svod_volume_descriptor; + }; + xe::be data_file_count; + xe::be data_file_size; + xe::be volume_type; + xe::be online_creator; + xe::be category; + uint8_t reserved2[0x20]; + union { + XContentMediaData media_data; + XContentAvatarAssetData avatar_asset_data; }; - uint32_t data_file_count; - uint64_t data_file_combined_size; - StfsDescriptorType descriptor_type; uint8_t device_id[0x14]; - wchar_t display_names[0x900 / 2]; - wchar_t display_descs[0x900 / 2]; - wchar_t publisher_name[0x80 / 2]; - wchar_t title_name[0x80 / 2]; - uint8_t transfer_flags; - uint32_t thumbnail_image_size; - uint32_t title_thumbnail_image_size; - uint8_t thumbnail_image[0x4000]; - uint8_t title_thumbnail_image[0x4000]; + wchar_t display_name[9][0x80]; + wchar_t description[9][0x80]; + wchar_t publisher[0x40]; + wchar_t title_name[0x40]; + union { + XContentAttributes bits; + uint8_t as_byte; + } flags; + xe::be thumbnail_size; + xe::be title_thumbnail_size; + uint8_t thumbnail[0x3D00]; + wchar_t display_name_ex[3][0x80]; + uint8_t title_thumbnail[0x3D00]; + wchar_t description_ex[3][0x80]; - // Metadata v2 Fields - uint8_t series_id[0x10]; - uint8_t season_id[0x10]; - int16_t season_number; - int16_t episode_number; - wchar_t additonal_display_names[0x300 / 2]; - wchar_t additional_display_descriptions[0x300 / 2]; + std::wstring get_display_name(uint32_t locale) { + uint32_t locale_id = locale; + locale_id--; + + wchar_t* str = 0; + if (locale_id >= 0 && locale_id < 9) { + str = display_name[locale_id]; + } else if (locale_id >= 9 && locale_id < 12 && metadata_version >= 2) { + str = display_name_ex[locale_id - 9]; + } + if (!str) { + return L""; + } + + std::vector wstr; + wstr.resize(wcslen(str) + 1); // add 1 in case wcslen returns 0 + xe::copy_and_swap((wchar_t*)wstr.data(), str, wcslen(str)); + + return std::wstring(wstr.data()); + } + std::wstring get_description(uint32_t locale) { + uint32_t locale_id = locale; + locale_id--; + + wchar_t* str = 0; + if (locale_id >= 0 && locale_id < 9) { + str = display_name[locale_id]; + } else if (locale_id >= 9 && locale_id < 12 && metadata_version >= 2) { + str = display_name_ex[locale_id - 9]; + } + if (!str) { + return L""; + } + + std::vector wstr; + wstr.resize(wcslen(str) + 1); // add 1 in case wcslen returns 0 + xe::copy_and_swap(wstr.data(), str, wcslen(str)); + + return std::wstring(wstr.data()); + } + std::wstring get_publisher() { + std::vector wstr; + wstr.resize(wcslen(publisher) + 1); // add 1 in case wcslen returns 0 + xe::copy_and_swap(wstr.data(), publisher, wcslen(publisher)); + + return std::wstring(wstr.data()); + } + std::wstring get_title_name() { + std::vector wstr; + wstr.resize(wcslen(title_name) + 1); // add 1 in case wcslen returns 0 + xe::copy_and_swap(wstr.data(), title_name, wcslen(title_name)); + + return std::wstring(wstr.data()); + } }; +static_assert_size(XContentMetadata, 0x93D6); +#pragma pack(pop) + +struct XContentInstallerUpdate { + xe::be base_version; + xe::be new_version; + uint8_t reserved[0x15E8]; +}; +static_assert_size(XContentInstallerUpdate, 0x15F0); + +struct XOnlineContentResumeHeader { + xe::be resume_state; + xe::be current_file_index; + xe::be current_file_offset; + xe::be bytes_processed; + xe::be last_modified; +}; +static_assert_size(XOnlineContentResumeHeader, 0x20); + +struct XContentInstallerProgressCache { + XOnlineContentResumeHeader resume_header; + uint8_t cab_resume_data[0x15D0]; +}; +static_assert_size(XContentInstallerProgressCache, 0x15F0); + +struct XContentLicense { + xe::be licensee_id; + xe::be license_bits; + xe::be license_flags; +}; +static_assert_size(XContentLicense, 0x10); + +enum XContentPackageType : uint32_t { + kPackageTypeCon = 0x434F4E20, + kPackageTypePirs = 0x50495253, + kPackageTypeLive = 0x4C495645, +}; + +#pragma pack(push, 1) +struct XContentHeader { + xe::be magic; + uint8_t signature[0x228]; + XContentLicense licenses[0x10]; + uint8_t content_id[0x14]; + xe::be header_size; +}; +static_assert_size(XContentHeader, 0x344); + +struct XContentInstaller { + xe::be type; + union { + XContentInstallerUpdate update; + XContentInstallerProgressCache install_progress_cache; + } metadata; +}; +static_assert_size(XContentInstaller, 0x15F4); + +struct StfsHeader { + XContentHeader header; + XContentMetadata metadata; + XContentInstaller installer; + uint8_t padding[0x2F2]; +}; +static_assert_size(StfsHeader, 0xB000); +#pragma pack(pop) class StfsContainerDevice : public Device { public: @@ -206,6 +354,13 @@ class StfsContainerDevice : public Device { kErrorDamagedFile = -31, }; + enum class SvodLayoutType { + kUnknown = 0x0, + kEnhancedGDF = 0x1, + kXSF = 0x2, + kSingleFile = 0x4, + }; + struct BlockHash { uint32_t next_block_index; uint32_t info; @@ -213,11 +368,10 @@ class StfsContainerDevice : public Device { const uint32_t kSTFSDataBlocksPerHashLevel[3] = {0xAA, 0x70E4, 0x4AF768}; + const char* ReadMagic(const std::wstring& path); bool ResolveFromFolder(const std::wstring& path); Error MapFiles(); - static Error ReadPackageType(const uint8_t* map_ptr, size_t map_size, - StfsPackageType* package_type_out); Error ReadHeaderAndVerify(const uint8_t* map_ptr, size_t map_size); Error ReadSVOD(); @@ -252,8 +406,8 @@ class StfsContainerDevice : public Device { size_t base_offset_; size_t magic_offset_; std::unique_ptr root_entry_; - StfsPackageType package_type_; StfsHeader header_; + SvodLayoutType svod_layout_; }; } // namespace vfs diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index da801a7bf..610a20a06 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -356,6 +356,19 @@ static_assert_size(X_EXCEPTION_RECORD, 0x50); #pragma pack(pop) +// Found by dumping the kSectionStringTable sections of various games: +enum class XLanguage : uint32_t { + kUnknown = 0, + kEnglish = 1, + kJapanese = 2, + kGerman = 3, + kFrench = 4, + kSpanish = 5, + kItalian = 6, + kKorean = 7, + kChinese = 8, +}; + } // namespace xe // clang-format on