Files
Xenia-Canary/src/xenia/vfs/devices/stfs_container_device.cc
2015-06-27 22:37:49 -07:00

316 lines
12 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/stfs_container_device.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/vfs/devices/stfs_container_entry.h"
namespace xe {
namespace vfs {
#define XEGETUINT24BE(p) \
(((uint32_t)xe::load_and_swap<uint8_t>((p) + 0) << 16) | \
((uint32_t)xe::load_and_swap<uint8_t>((p) + 1) << 8) | \
(uint32_t)xe::load_and_swap<uint8_t>((p) + 2))
#define XEGETUINT24LE(p) \
(((uint32_t)xe::load<uint8_t>((p) + 2) << 16) | \
((uint32_t)xe::load<uint8_t>((p) + 1) << 8) | \
(uint32_t)xe::load<uint8_t>((p) + 0))
STFSContainerDevice::STFSContainerDevice(const std::string& mount_path,
const std::wstring& local_path)
: Device(mount_path), local_path_(local_path) {}
STFSContainerDevice::~STFSContainerDevice() = default;
bool STFSContainerDevice::Initialize() {
mmap_ = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
if (!mmap_) {
XELOGE("STFS container could not be mapped");
return false;
}
uint8_t* map_ptr = mmap_->data();
auto result = ReadHeaderAndVerify(map_ptr);
if (result != Error::kSuccess) {
XELOGE("STFS header read/verification failed: %d", result);
return false;
}
result = ReadAllEntries(map_ptr);
if (result != Error::kSuccess) {
XELOGE("STFS entry reading failed: %d", result);
return false;
}
return true;
}
STFSContainerDevice::Error STFSContainerDevice::ReadHeaderAndVerify(
const uint8_t* map_ptr) {
// Check signature.
if (memcmp(map_ptr, "LIVE", 4) == 0) {
package_type_ = StfsPackageType::STFS_PACKAGE_LIVE;
} else if (memcmp(map_ptr, "PIRS", 4) == 0) {
package_type_ = StfsPackageType::STFS_PACKAGE_PIRS;
} else if (memcmp(map_ptr, "CON", 3) == 0) {
package_type_ = StfsPackageType::STFS_PACKAGE_CON;
} else {
// Unexpected format.
return Error::kErrorFileMismatch;
}
// Read header.
if (!header_.Read(map_ptr)) {
return Error::kErrorDamagedFile;
}
if (((header_.header_size + 0x0FFF) & 0xB000) == 0xB000) {
table_size_shift_ = 0;
} else {
table_size_shift_ = 1;
}
return Error::kSuccess;
}
STFSContainerDevice::Error STFSContainerDevice::ReadAllEntries(
const uint8_t* map_ptr) {
auto root_entry = new STFSContainerEntry(this, "", mmap_.get());
root_entry->attributes_ = kFileAttributeDirectory;
root_entry_ = std::unique_ptr<Entry>(root_entry);
std::vector<STFSContainerEntry*> all_entries;
// Load all listings.
auto& volume_descriptor = header_.volume_descriptor;
uint32_t table_block_index = volume_descriptor.file_table_block_number;
for (size_t n = 0; n < volume_descriptor.file_table_block_count; n++) {
const uint8_t* p =
map_ptr + BlockToOffset(ComputeBlockNumber(table_block_index));
for (size_t m = 0; m < 0x1000 / 0x40; m++) {
const uint8_t* filename = p; // 0x28b
if (filename[0] == 0) {
// Done.
break;
}
uint8_t filename_length_flags = xe::load_and_swap<uint8_t>(p + 0x28);
uint32_t allocated_block_count = XEGETUINT24LE(p + 0x29);
uint32_t start_block_index = XEGETUINT24LE(p + 0x2F);
uint16_t path_indicator = xe::load_and_swap<uint16_t>(p + 0x32);
uint32_t file_size = xe::load_and_swap<uint32_t>(p + 0x34);
uint32_t update_timestamp = xe::load_and_swap<uint32_t>(p + 0x38);
uint32_t access_timestamp = xe::load_and_swap<uint32_t>(p + 0x3C);
p += 0x40;
auto entry = new STFSContainerEntry(
this, std::string((char*)filename, filename_length_flags & 0x3F),
mmap_.get());
// bit 0x40 = consecutive blocks (not fragmented?)
if (filename_length_flags & 0x80) {
entry->attributes_ = kFileAttributeDirectory;
} else {
entry->attributes_ = kFileAttributeNormal | kFileAttributeReadOnly;
entry->data_offset_ =
BlockToOffset(ComputeBlockNumber(start_block_index));
entry->data_size_ = file_size;
}
entry->size_ = file_size;
entry->allocation_size_ = xe::round_up(file_size, bytes_per_sector());
entry->create_timestamp_ = update_timestamp;
entry->access_timestamp_ = access_timestamp;
entry->write_timestamp_ = update_timestamp;
all_entries.push_back(entry);
// Fill in all block records.
// It's easier to do this now and just look them up later, at the cost
// of some memory. Nasty chain walk.
// TODO(benvanik): optimize if flag 0x40 (consecutive) is set.
if (entry->attributes() & X_FILE_ATTRIBUTE_NORMAL) {
uint32_t block_index = start_block_index;
size_t remaining_size = file_size;
uint32_t info = 0x80;
while (remaining_size && block_index && info >= 0x80) {
size_t block_size = std::min(0x1000ull, remaining_size);
size_t offset = BlockToOffset(ComputeBlockNumber(block_index));
entry->block_list_.push_back({offset, block_size});
remaining_size -= block_size;
auto block_hash = GetBlockHash(map_ptr, block_index, 0);
if (table_size_shift_ && block_hash.info < 0x80) {
block_hash = GetBlockHash(map_ptr, block_index, 1);
}
block_index = block_hash.next_block_index;
info = block_hash.info;
}
}
if (path_indicator == 0xFFFF) {
// Root entry.
root_entry->children_.emplace_back(std::unique_ptr<Entry>(entry));
} else {
// Lookup and add.
auto parent = all_entries[path_indicator];
parent->children_.emplace_back(std::unique_ptr<Entry>(entry));
}
}
auto block_hash = GetBlockHash(map_ptr, table_block_index, 0);
if (table_size_shift_ && block_hash.info < 0x80) {
block_hash = GetBlockHash(map_ptr, table_block_index, 1);
}
table_block_index = block_hash.next_block_index;
if (table_block_index == 0xFFFFFF) {
break;
}
}
return Error::kSuccess;
}
size_t STFSContainerDevice::BlockToOffset(uint32_t block) {
if (block >= 0xFFFFFF) {
return -1;
} else {
return ((header_.header_size + 0x0FFF) & 0xF000) + (block << 12);
}
}
uint32_t STFSContainerDevice::ComputeBlockNumber(uint32_t block_index) {
uint32_t block_shift = 0;
if (((header_.header_size + 0x0FFF) & 0xB000) == 0xB000) {
block_shift = 1;
} else {
if ((header_.volume_descriptor.block_separation & 0x1) == 0x1) {
block_shift = 0;
} else {
block_shift = 1;
}
}
uint32_t base = (block_index + 0xAA) / 0xAA;
if (package_type_ == StfsPackageType::STFS_PACKAGE_CON) {
base <<= block_shift;
}
uint32_t block = base + block_index;
if (block_index >= 0xAA) {
base = (block_index + 0x70E4) / 0x70E4;
if (package_type_ == StfsPackageType::STFS_PACKAGE_CON) {
base <<= block_shift;
}
block += base;
if (block_index >= 0x70E4) {
base = (block_index + 0x4AF768) / 0x4AF768;
if (package_type_ == StfsPackageType::STFS_PACKAGE_CON) {
base <<= block_shift;
}
block += base;
}
}
return block;
}
STFSContainerDevice::BlockHash STFSContainerDevice::GetBlockHash(
const uint8_t* map_ptr, uint32_t block_index, uint32_t table_offset) {
static const uint32_t table_spacing[] = {
0xAB, 0x718F,
0xFE7DA, // The distance in blocks between tables
0xAC, 0x723A,
0xFD00B, // For when tables are 1 block and when they are 2 blocks
};
uint32_t record = block_index % 0xAA;
uint32_t table_index =
(block_index / 0xAA) * table_spacing[table_size_shift_ * 3 + 0];
if (block_index >= 0xAA) {
table_index += ((block_index / 0x70E4) + 1) << table_size_shift_;
if (block_index >= 0x70E4) {
table_index += 1 << table_size_shift_;
}
}
// table_index += table_offset - (1 << table_size_shift_);
const uint8_t* hash_data = map_ptr + BlockToOffset(table_index);
const uint8_t* record_data = hash_data + record * 0x18;
uint32_t info = xe::load_and_swap<uint8_t>(record_data + 0x14);
uint32_t next_block_index = XEGETUINT24BE(record_data + 0x15);
return {next_block_index, info};
}
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;
}
reserved = xe::load_and_swap<uint8_t>(p + 0x01);
block_separation = xe::load_and_swap<uint8_t>(p + 0x02);
file_table_block_count = xe::load_and_swap<uint16_t>(p + 0x03);
file_table_block_number = XEGETUINT24BE(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 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);
if (metadata_version > 1) {
// This is a variant of thumbnail data/etc.
// Can just ignore it for now (until we parse thumbnails).
XELOGW("STFSContainer doesn't support version %d yet", metadata_version);
}
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<uint8_t>(p + 0x3A9);
if (descriptor_type != StfsDescriptorType::STFS_DESCRIPTOR_STFS) {
XELOGE("STFS descriptor format not supported: %d", descriptor_type);
return false;
}
if (!volume_descriptor.Read(p + 0x379)) {
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);
return true;
}
} // namespace vfs
} // namespace xe