[Kernel/VFS] Improve STFS/XContent structures
Mostly the same as 6a3c712aad
This commit is contained in:
parent
266d987fc7
commit
20c612cd42
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue