[Kernel/VFS] Improve STFS/XContent structures

Mostly the same as 6a3c712aad
This commit is contained in:
emoose 2020-01-11 18:15:59 +00:00 committed by illusion
parent 266d987fc7
commit 20c612cd42
9 changed files with 381 additions and 357 deletions

View File

@ -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);

View File

@ -80,9 +80,6 @@ std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
uint32_t content_type) {
std::vector<XCONTENT_DATA> 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<XCONTENT_DATA> 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<uint32_t>(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<uint32_t>(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<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
result.emplace_back(std::move(content_data));
}
delete header;
return result;
}

View File

@ -114,19 +114,15 @@ X_RESULT FolderContentPackage::GetThumbnail(std::vector<uint8_t>* 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<uint8_t>* 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;
}

View File

@ -524,7 +524,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) {
std::vector<xdbf::Achievement> 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

View File

@ -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<uint16_t>(SpaSection::kStringTable),
static_cast<uint64_t>(locale));
static_cast<uint64_t>(language));
if (!xstr_table) {
return "";
}
@ -181,7 +181,7 @@ std::string SpaFile::GetStringTableEntry(Locale locale,
}
uint32_t SpaFile::GetAchievements(
Locale locale, std::vector<Achievement>* achievements) const {
XLanguage lang, std::vector<Achievement>* achievements) const {
auto xach_table = GetEntry(static_cast<uint16_t>(SpaSection::kMetadata),
static_cast<uint64_t>(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<uint16_t>(SpaSection::kStringTable),
static_cast<uint64_t>(locale));
static_cast<uint64_t>(lang));
if (!xstr_table) {
return 0;
}
@ -240,21 +240,21 @@ Entry* SpaFile::GetIcon() const {
static_cast<uint64_t>(SpaID::Title));
}
Locale SpaFile::GetDefaultLocale() const {
XLanguage SpaFile::GetDefaultLanguage() const {
auto block = GetEntry(static_cast<uint16_t>(SpaSection::kMetadata),
static_cast<uint64_t>(SpaID::Xstc));
if (!block) {
return Locale::kEnglish;
return XLanguage::kEnglish;
}
auto xstc = reinterpret_cast<const X_XDBF_XSTC_DATA*>(block->data.data());
assert_true(xstc->header.magic == static_cast<uint32_t>(SpaID::Xstc));
return static_cast<Locale>(static_cast<uint32_t>(xstc->default_language));
return static_cast<XLanguage>(static_cast<uint32_t>(xstc->default_language));
}
std::string SpaFile::GetTitleName() const {
return GetStringTableEntry(GetDefaultLocale(),
return GetStringTableEntry(GetDefaultLanguage(),
static_cast<uint16_t>(SpaID::Title));
}

View File

@ -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<Achievement>* achievements) const;
Entry* GetIcon() const;
Locale GetDefaultLocale() const;
XLanguage GetDefaultLanguage() const;
std::string GetTitleName() const;
bool GetTitleData(X_XDBF_XTHD_DATA* title_data) const;
};

View File

@ -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<StfsContainerEntry*> 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<uint8_t>(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<uint8_t>(p + 0x01);
flags = xe::load_and_swap<uint8_t>(p + 0x02);
file_table_block_count = xe::load<uint16_t>(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<uint32_t>(p + 0x1C);
total_unallocated_block_count = xe::load_and_swap<uint32_t>(p + 0x20);
return true;
}
bool SvodVolumeDescriptor::Read(const uint8_t* p) {
descriptor_size = xe::load<uint8_t>(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<uint8_t>(p + 0x01);
worker_thread_processor = xe::load<uint8_t>(p + 0x02);
worker_thread_priority = xe::load<uint8_t>(p + 0x03);
std::memcpy(hash, p + 0x04, 0x14);
device_features = xe::load<uint8_t>(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<uint32_t>(p + 0x340);
content_type = (StfsContentType)xe::load_and_swap<uint32_t>(p + 0x344);
metadata_version = xe::load_and_swap<uint32_t>(p + 0x348);
content_size = xe::load_and_swap<uint32_t>(p + 0x34C);
media_id = xe::load_and_swap<uint32_t>(p + 0x354);
version = xe::load_and_swap<uint32_t>(p + 0x358);
base_version = xe::load_and_swap<uint32_t>(p + 0x35C);
title_id = xe::load_and_swap<uint32_t>(p + 0x360);
platform = (StfsPlatform)xe::load_and_swap<uint8_t>(p + 0x364);
executable_type = xe::load_and_swap<uint8_t>(p + 0x365);
disc_number = xe::load_and_swap<uint8_t>(p + 0x366);
disc_in_set = xe::load_and_swap<uint8_t>(p + 0x367);
save_game_id = xe::load_and_swap<uint32_t>(p + 0x368);
std::memcpy(console_id, p + 0x36C, 0x5);
std::memcpy(profile_id, p + 0x371, 0x8);
data_file_count = xe::load_and_swap<uint32_t>(p + 0x39D);
data_file_combined_size = xe::load_and_swap<uint64_t>(p + 0x3A1);
descriptor_type = (StfsDescriptorType)xe::load_and_swap<uint32_t>(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<uint16_t>(p + 0x411 + n * 2);
display_descs[n] = xe::load_and_swap<uint16_t>(p + 0xD11 + n * 2);
}
for (size_t n = 0; n < 0x80 / 2; n++) {
publisher_name[n] = xe::load_and_swap<uint16_t>(p + 0x1611 + n * 2);
title_name[n] = xe::load_and_swap<uint16_t>(p + 0x1691 + n * 2);
}
transfer_flags = xe::load_and_swap<uint8_t>(p + 0x1711);
thumbnail_image_size = xe::load_and_swap<uint32_t>(p + 0x1712);
title_thumbnail_image_size = xe::load_and_swap<uint32_t>(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<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);
}
}
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<uint32_t>(map->data());
auto magic_bytes = static_cast<char*>(static_cast<void*>(&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;
}
}

View File

@ -15,6 +15,7 @@
#include <string>
#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<uint32_t> allocated_block_count;
xe::be<uint32_t> 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<uint16_t> season_number;
xe::be<uint16_t> episode_number;
};
static_assert_size(XContentMediaData, 0x24);
#pragma pack(push, 1)
struct XContentAvatarAssetData {
xe::be<uint32_t> sub_category;
xe::be<uint32_t> 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<XContentType> content_type;
xe::be<uint32_t> metadata_version;
xe::be<uint64_t> content_size;
xex2_opt_execution_info execution_info;
uint8_t console_id[5];
xe::be<uint64_t> profile_id;
union {
StfsVolumeDescriptor stfs_volume_descriptor;
SvodVolumeDescriptor svod_volume_descriptor;
SvodDeviceDescriptor svod_volume_descriptor;
};
xe::be<uint32_t> data_file_count;
xe::be<uint64_t> data_file_size;
xe::be<XContentVolumeType> volume_type;
xe::be<uint64_t> online_creator;
xe::be<uint32_t> 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<uint32_t> thumbnail_size;
xe::be<uint32_t> 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<wchar_t> wstr;
wstr.resize(wcslen(str) + 1); // add 1 in case wcslen returns 0
xe::copy_and_swap<wchar_t>((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<wchar_t> 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_publisher() {
std::vector<wchar_t> wstr;
wstr.resize(wcslen(publisher) + 1); // add 1 in case wcslen returns 0
xe::copy_and_swap<wchar_t>(wstr.data(), publisher, wcslen(publisher));
return std::wstring(wstr.data());
}
std::wstring get_title_name() {
std::vector<wchar_t> wstr;
wstr.resize(wcslen(title_name) + 1); // add 1 in case wcslen returns 0
xe::copy_and_swap<wchar_t>(wstr.data(), title_name, wcslen(title_name));
return std::wstring(wstr.data());
}
};
static_assert_size(XContentMetadata, 0x93D6);
#pragma pack(pop)
struct XContentInstallerUpdate {
xe::be<uint32_t> base_version;
xe::be<uint32_t> new_version;
uint8_t reserved[0x15E8];
};
static_assert_size(XContentInstallerUpdate, 0x15F0);
struct XOnlineContentResumeHeader {
xe::be<uint32_t> resume_state;
xe::be<uint32_t> current_file_index;
xe::be<uint64_t> current_file_offset;
xe::be<uint64_t> bytes_processed;
xe::be<uint64_t> 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<uint64_t> licensee_id;
xe::be<uint32_t> license_bits;
xe::be<uint32_t> 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<XContentPackageType> magic;
uint8_t signature[0x228];
XContentLicense licenses[0x10];
uint8_t content_id[0x14];
xe::be<uint32_t> header_size;
};
static_assert_size(XContentHeader, 0x344);
struct XContentInstaller {
xe::be<uint32_t> 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<Entry> root_entry_;
StfsPackageType package_type_;
StfsHeader header_;
SvodLayoutType svod_layout_;
};
} // namespace vfs

View File

@ -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