[VFS] Rewrote STFS to use memory mapping

- Replaced old-style buffer, buffer_length with std::span
- Added STFS and SVOD specific Entry and File classes
- Other smaller improvements
This commit is contained in:
Gliniak 2025-05-13 21:05:46 +02:00 committed by Radosław Gliński
parent e85c2392ba
commit fe739208b6
36 changed files with 481 additions and 206 deletions

View File

@ -412,8 +412,9 @@ bool AudioMediaPlayer::LoadSongToMemory(std::vector<uint8_t>* buffer) {
buffer->resize(vfs_file->entry()->size());
size_t bytes_read = 0;
result = vfs_file->ReadSync(buffer->data(), vfs_file->entry()->size(), 0,
&bytes_read);
result = vfs_file->ReadSync(
std::span<uint8_t>(buffer->data(), vfs_file->entry()->size()), 0,
&bytes_read);
return !result;
}

View File

@ -157,8 +157,8 @@ bool KernelState::UpdateSpaData(vfs::Entry* spa_file_update) {
std::vector<uint8_t> data(spa_file_update->size());
size_t read_bytes = 0;
if (file->ReadSync(data.data(), spa_file_update->size(), 0, &read_bytes) !=
X_STATUS_SUCCESS) {
if (file->ReadSync(std::span<uint8_t>(data.data(), spa_file_update->size()),
0, &read_bytes) != X_STATUS_SUCCESS) {
return false;
}

View File

@ -102,7 +102,8 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
// Read entire file into memory.
// Ugh.
size_t bytes_read = 0;
result = file->ReadSync(buffer.data(), buffer.size(), 0, &bytes_read);
result = file->ReadSync(std::span<uint8_t>(buffer.data(), buffer.size()), 0,
&bytes_read);
if (XFAILED(result)) {
return result;
}

View File

@ -11,6 +11,7 @@
#define XENIA_KERNEL_UTIL_XEX2_INFO_H_
#include <cstdint>
#include <string>
#include <unordered_map>
#include "xenia/base/byte_order.h"

View File

@ -202,8 +202,9 @@ bool ProfileManager::LoadAccount(const uint64_t xuid) {
file_data.resize(output_file->entry()->size());
size_t bytes_read = 0;
output_file->ReadSync(file_data.data(), output_file->entry()->size(), 0,
&bytes_read);
output_file->ReadSync(
std::span<uint8_t>(file_data.data(), output_file->entry()->size()), 0,
&bytes_read);
output_file->Destroy();
if (bytes_read < sizeof(X_XAMACCOUNTINFO)) {
@ -562,8 +563,9 @@ bool ProfileManager::UpdateAccount(const uint64_t xuid,
EncryptAccountFile(account, encrypted_data.data());
size_t written_bytes = 0;
output_file->WriteSync(encrypted_data.data(), encrypted_data.size(), 0,
&written_bytes);
output_file->WriteSync(
std::span<uint8_t>(encrypted_data.data(), encrypted_data.size()), 0,
&written_bytes);
output_file->Destroy();
return true;
}

View File

@ -89,7 +89,8 @@ void UserProfile::LoadProfileIcon(XTileType tile_type) {
std::vector<uint8_t> data(file->entry()->size());
size_t written_bytes = 0;
file->ReadSync(data.data(), file->entry()->size(), 0, &written_bytes);
file->ReadSync(std::span<uint8_t>(data.data(), file->entry()->size()), 0,
&written_bytes);
file->Destroy();
profile_images_.insert({tile_type, data});
@ -114,7 +115,8 @@ std::vector<uint8_t> UserProfile::LoadGpd(const uint32_t title_id) {
std::vector<uint8_t> data(entry->size());
size_t read_size = 0;
result = file->ReadSync(data.data(), entry->size(), 0, &read_size);
result = file->ReadSync(std::span<uint8_t>(data.data(), entry->size()), 0,
&read_size);
if (result != X_STATUS_SUCCESS || read_size != entry->size()) {
XELOGW(
"User {} (XUID: {:016X}) cannot read profile GPD! Status: {:08X} read: "
@ -150,7 +152,8 @@ bool UserProfile::WriteGpd(const uint32_t title_id) {
}
size_t written_bytes = 0;
file->WriteSync(data.data(), data.size(), 0, &written_bytes);
file->WriteSync(std::span<uint8_t>(data.data(), data.size()), 0,
&written_bytes);
file->Destroy();
return true;
}

View File

@ -277,7 +277,9 @@ dword_result_t XexLoadImageHeaders_entry(pointer_t<X_ANSI_STRING> path,
size_t bytes_read = 0;
X_STATUS result_status = vfs_file->ReadSync(
reinterpret_cast<void*>(header.host_address()), 2048, 0, &bytes_read);
std::span<uint8_t>(reinterpret_cast<uint8_t*>(header.host_address()),
2048),
0, &bytes_read);
if (result_status < 0) {
vfs_file->Destroy();
@ -298,8 +300,10 @@ dword_result_t XexLoadImageHeaders_entry(pointer_t<X_ANSI_STRING> path,
result_status = X_STATUS_SUCCESS;
} else {
result_status = vfs_file->ReadSync(
reinterpret_cast<void*>(header.host_address() + 2048),
header_size - 2048, 2048, &bytes_read);
std::span<uint8_t>(
reinterpret_cast<uint8_t*>(header.host_address() + 2048),
header_size - 2048),
2048, &bytes_read);
if (result_status >= X_STATUS_SUCCESS) {
result_status = X_STATUS_SUCCESS;
}

View File

@ -143,12 +143,14 @@ X_STATUS XFile::Read(uint32_t buffer_guest_address, uint32_t buffer_length,
result = X_STATUS_ACCESS_VIOLATION;
} else {
result = file_->ReadSync(
buffer_physical_heap
? memory()->TranslatePhysical(
buffer_physical_heap->GetPhysicalAddress(
buffer_guest_address))
: memory()->TranslateVirtual(buffer_guest_address),
buffer_length, size_t(byte_offset), &bytes_read);
std::span<uint8_t>(
buffer_physical_heap
? memory()->TranslatePhysical(
buffer_physical_heap->GetPhysicalAddress(
buffer_guest_address))
: memory()->TranslateVirtual(buffer_guest_address),
buffer_length),
size_t(byte_offset), &bytes_read);
if (XSUCCEEDED(result)) {
if (buffer_physical_heap) {
buffer_physical_heap->TriggerCallbacks(
@ -245,9 +247,10 @@ X_STATUS XFile::Write(uint32_t buffer_guest_address, uint32_t buffer_length,
}
size_t bytes_written = 0;
X_STATUS result =
file_->WriteSync(memory()->TranslateVirtual(buffer_guest_address),
buffer_length, size_t(byte_offset), &bytes_written);
X_STATUS result = file_->WriteSync(
std::span<uint8_t>(memory()->TranslateVirtual(buffer_guest_address),
buffer_length),
size_t(byte_offset), &bytes_written);
if (XSUCCEEDED(result)) {
position_ += bytes_written;
}

View File

@ -23,8 +23,8 @@ DiscImageFile::~DiscImageFile() = default;
void DiscImageFile::Destroy() { delete this; }
X_STATUS DiscImageFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {
X_STATUS DiscImageFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) {
if (byte_offset >= entry_->size()) {
return X_STATUS_END_OF_FILE;
}
@ -36,8 +36,8 @@ X_STATUS DiscImageFile::ReadSync(void* buffer, size_t buffer_length,
size_t real_offset = entry_->data_offset() + byte_offset;
size_t real_length =
std::min(buffer_length, entry_->data_size() - byte_offset);
std::memcpy(buffer, entry_->mmap()->data() + real_offset, real_length);
std::min(buffer.size(), entry_->data_size() - byte_offset);
std::memcpy(buffer.data(), entry_->mmap()->data() + real_offset, real_length);
*out_bytes_read = real_length;
return X_STATUS_SUCCESS;
}

View File

@ -24,10 +24,10 @@ class DiscImageFile : public File {
void Destroy() override;
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) override;
X_STATUS WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) override {
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override {
return X_STATUS_ACCESS_DENIED;
}
X_STATUS SetLength(size_t length) override { return X_STATUS_ACCESS_DENIED; }

View File

@ -25,19 +25,23 @@ DiscZarchiveFile::~DiscZarchiveFile() = default;
void DiscZarchiveFile::Destroy() { delete this; }
X_STATUS DiscZarchiveFile::ReadSync(void* buffer, size_t buffer_length,
X_STATUS DiscZarchiveFile::ReadSync(std::span<uint8_t> buffer,
size_t byte_offset,
size_t* out_bytes_read) {
if (byte_offset >= entry_->size()) {
return X_STATUS_END_OF_FILE;
}
const uint64_t bytes_read =
((DiscZarchiveDevice*)entry_->device_)
->reader()
->ReadFromFile(entry_->handle_, byte_offset, buffer_length, buffer);
const size_t real_length =
std::min(buffer_length, entry_->data_size() - byte_offset);
*out_bytes_read = real_length;
DiscZarchiveDevice* zArchDev =
dynamic_cast<DiscZarchiveDevice*>(entry_->device_);
if (!zArchDev) {
return X_STATUS_UNSUCCESSFUL;
}
const uint64_t bytes_read = zArchDev->reader()->ReadFromFile(
entry_->handle_, byte_offset, buffer.size(), buffer.data());
*out_bytes_read = bytes_read;
return X_STATUS_SUCCESS;
}

View File

@ -24,10 +24,10 @@ class DiscZarchiveFile : public File {
void Destroy() override;
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) override;
X_STATUS WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) override {
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override {
return X_STATUS_ACCESS_DENIED;
}
X_STATUS SetLength(size_t length) override { return X_STATUS_ACCESS_DENIED; }

View File

@ -28,21 +28,22 @@ void HostPathFile::Destroy() {
delete this;
}
X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {
X_STATUS HostPathFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) {
if (!(file_access_ &
(FileAccess::kGenericRead | FileAccess::kFileReadData))) {
return X_STATUS_ACCESS_DENIED;
}
if (file_handle_->Read(byte_offset, buffer, buffer_length, out_bytes_read)) {
if (file_handle_->Read(byte_offset, buffer.data(), buffer.size(),
out_bytes_read)) {
return X_STATUS_SUCCESS;
} else {
return X_STATUS_END_OF_FILE;
}
}
X_STATUS HostPathFile::WriteSync(const void* buffer, size_t buffer_length,
X_STATUS HostPathFile::WriteSync(std::span<const uint8_t> buffer,
size_t byte_offset,
size_t* out_bytes_written) {
if (!(file_access_ & (FileAccess::kGenericWrite | FileAccess::kFileWriteData |
@ -50,7 +51,7 @@ X_STATUS HostPathFile::WriteSync(const void* buffer, size_t buffer_length,
return X_STATUS_ACCESS_DENIED;
}
if (file_handle_->Write(byte_offset, buffer, buffer_length,
if (file_handle_->Write(byte_offset, buffer.data(), buffer.size(),
out_bytes_written)) {
return X_STATUS_SUCCESS;
} else {

View File

@ -28,10 +28,10 @@ class HostPathFile : public File {
void Destroy() override;
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) override;
X_STATUS WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) override;
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override;
X_STATUS SetLength(size_t length) override;
private:

View File

@ -21,8 +21,8 @@ NullFile::~NullFile() = default;
void NullFile::Destroy() { delete this; }
X_STATUS NullFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {
X_STATUS NullFile::ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) {
if (!(file_access_ & FileAccess::kFileReadData)) {
return X_STATUS_ACCESS_DENIED;
}
@ -30,7 +30,7 @@ X_STATUS NullFile::ReadSync(void* buffer, size_t buffer_length,
return X_STATUS_SUCCESS;
}
X_STATUS NullFile::WriteSync(const void* buffer, size_t buffer_length,
X_STATUS NullFile::WriteSync(std::span<const uint8_t> buffer,
size_t byte_offset, size_t* out_bytes_written) {
if (!(file_access_ &
(FileAccess::kFileWriteData | FileAccess::kFileAppendData))) {

View File

@ -27,10 +27,10 @@ class NullFile : public File {
void Destroy() override;
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) override;
X_STATUS WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) override;
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override;
X_STATUS SetLength(size_t length) override;
};

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -100,7 +100,7 @@ bool XContentContainerDevice::Initialize() {
SetupContainer();
if (LoadHostFiles(header_file) != Result::kSuccess) {
if (LoadHostFiles() != Result::kSuccess) {
XELOGE("Error loading XContent host files.");
return false;
}
@ -152,14 +152,6 @@ void XContentContainerDevice::Dump(StringBuffer* string_buffer) {
root_entry_->Dump(string_buffer, 0);
}
void XContentContainerDevice::CloseFiles() {
for (auto& file : files_) {
fclose(file.second);
}
files_.clear();
files_total_size_ = 0;
}
kernel::xam::XCONTENT_AGGREGATE_DATA XContentContainerDevice::content_header()
const {
kernel::xam::XCONTENT_AGGREGATE_DATA data;

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -14,8 +14,6 @@
#include <map>
#include <string_view>
#include "xenia/base/math.h"
#include "xenia/kernel/util/xex2_info.h"
#include "xenia/kernel/xam/content_manager.h"
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/stfs_xbox.h"
@ -66,7 +64,7 @@ class XContentContainerDevice : public Device {
}
uint32_t content_type() const {
return (uint32_t)header_->content_metadata.content_type.get();
return static_cast<uint32_t>(header_->content_metadata.content_type.get());
}
kernel::xam::XCONTENT_AGGREGATE_DATA content_header() const;
@ -99,12 +97,11 @@ class XContentContainerDevice : public Device {
virtual Result Read() = 0;
// Load all host files. Usually STFS is only 1 file, meanwhile SVOD is usually
// multiple file.
virtual Result LoadHostFiles(FILE* header_file) = 0;
// Initialize any container specific fields.
virtual Result LoadHostFiles() = 0;
// Initialize container specific fields.
virtual void SetupContainer() {};
Entry* ResolvePath(const std::string_view path) override;
void CloseFiles();
void Dump(StringBuffer* string_buffer) override;
Result ReadHeaderAndVerify(FILE* header_file);
@ -119,7 +116,6 @@ class XContentContainerDevice : public Device {
std::string name_;
std::filesystem::path host_path_;
std::map<size_t, FILE*> files_;
size_t files_total_size_;
std::unique_ptr<Entry> root_entry_;
std::unique_ptr<XContentContainerHeader> header_;

View File

@ -2,46 +2,22 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_container_file.h"
#include <map>
namespace xe {
namespace vfs {
XContentContainerEntry::XContentContainerEntry(Device* device, Entry* parent,
const std::string_view path,
MultiFileHandles* files)
: Entry(device, parent, path),
files_(files),
data_offset_(0),
data_size_(0),
block_(0) {}
const std::string_view path)
: Entry(device, parent, path), data_offset_(0), data_size_(0), block_(0) {}
XContentContainerEntry::~XContentContainerEntry() = default;
std::unique_ptr<XContentContainerEntry> XContentContainerEntry::Create(
Device* device, Entry* parent, const std::string_view name,
MultiFileHandles* files) {
auto path = xe::utf8::join_guest_paths(parent->path(), name);
auto entry =
std::make_unique<XContentContainerEntry>(device, parent, path, files);
return std::move(entry);
}
X_STATUS XContentContainerEntry::Open(uint32_t desired_access,
File** out_file) {
*out_file = new XContentContainerFile(desired_access, this);
return X_STATUS_SUCCESS;
}
bool XContentContainerEntry::DeleteEntryInternal(Entry* entry) { return false; }
} // namespace vfs

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -10,8 +10,6 @@
#ifndef XENIA_VFS_DEVICES_XCONTENT_CONTAINER_ENTRY_H_
#define XENIA_VFS_DEVICES_XCONTENT_CONTAINER_ENTRY_H_
#include <map>
#include <string>
#include <vector>
#include "xenia/vfs/entry.h"
@ -19,26 +17,18 @@
namespace xe {
namespace vfs {
typedef std::map<size_t, FILE*> MultiFileHandles;
class XContentContainerDevice;
class XContentContainerEntry : public Entry {
public:
XContentContainerEntry(Device* device, Entry* parent,
const std::string_view path, MultiFileHandles* files);
const std::string_view path);
~XContentContainerEntry() override;
static std::unique_ptr<XContentContainerEntry> Create(
Device* device, Entry* parent, const std::string_view name,
MultiFileHandles* files);
MultiFileHandles* files() const { return files_; }
size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; }
size_t block() const { return block_; }
X_STATUS Open(uint32_t desired_access, File** out_file) override;
X_STATUS Open(uint32_t desired_access, File** out_file) override = 0;
struct BlockRecord {
size_t file;
@ -53,7 +43,6 @@ class XContentContainerEntry : public Entry {
bool DeleteEntryInternal(Entry* entry) override;
MultiFileHandles* files_;
size_t data_offset_;
size_t data_size_;
size_t block_;

View File

@ -2,17 +2,13 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <algorithm>
#include <cmath>
#include "xenia/base/math.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_container_file.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
namespace xe {
namespace vfs {
@ -25,7 +21,7 @@ XContentContainerFile::~XContentContainerFile() = default;
void XContentContainerFile::Destroy() { delete this; }
X_STATUS XContentContainerFile::ReadSync(void* buffer, size_t buffer_length,
X_STATUS XContentContainerFile::ReadSync(std::span<uint8_t> buffer,
size_t byte_offset,
size_t* out_bytes_read) {
if (byte_offset >= entry_->size()) {
@ -33,9 +29,9 @@ X_STATUS XContentContainerFile::ReadSync(void* buffer, size_t buffer_length,
}
size_t src_offset = 0;
uint8_t* p = reinterpret_cast<uint8_t*>(buffer);
uint8_t* p = buffer.data();
size_t remaining_length =
std::min(buffer_length, entry_->size() - byte_offset);
std::min(buffer.size(), entry_->size() - byte_offset);
*out_bytes_read = 0;
for (size_t i = 0; i < entry_->block_list().size(); i++) {
@ -51,9 +47,8 @@ X_STATUS XContentContainerFile::ReadSync(void* buffer, size_t buffer_length,
size_t read_length =
std::min(record.length - read_offset, remaining_length);
auto& file = entry_->files()->at(record.file);
xe::filesystem::Seek(file, record.offset + read_offset, SEEK_SET);
auto num_read = fread(p, 1, read_length, file);
auto num_read = Read(std::span<uint8_t>(p, read_length),
record.offset + read_offset, record.file);
*out_bytes_read += num_read;
p += num_read;

View File

@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@ -26,15 +26,18 @@ class XContentContainerFile : public File {
void Destroy() override;
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) override;
X_STATUS WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) override {
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override {
return X_STATUS_ACCESS_DENIED;
}
X_STATUS SetLength(size_t length) override { return X_STATUS_ACCESS_DENIED; }
private:
virtual size_t Read(std::span<uint8_t> buffer, size_t offset,
size_t record_file) = 0;
XContentContainerEntry* entry_;
};

View File

@ -7,13 +7,10 @@
******************************************************************************
*/
#include <algorithm>
#include <vector>
#include "xenia/vfs/devices/xcontent_devices/stfs_container_device.h"
#include "xenia/base/logging.h"
#include "xenia/kernel/xam/content_manager.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/stfs_container_device.h"
#include "xenia/vfs/devices/xcontent_devices/stfs_container_entry.h"
namespace xe {
namespace vfs {
@ -26,7 +23,7 @@ StfsContainerDevice::StfsContainerDevice(const std::string_view mount_path,
SetName("STFS");
}
StfsContainerDevice::~StfsContainerDevice() { CloseFiles(); }
StfsContainerDevice::~StfsContainerDevice() {}
void StfsContainerDevice::SetupContainer() {
// Additional part specific to STFS container.
@ -38,26 +35,27 @@ void StfsContainerDevice::SetupContainer() {
((kBlocksPerHashLevel[0] + 1) * blocks_per_hash_table_);
}
XContentContainerDevice::Result StfsContainerDevice::LoadHostFiles(
FILE* header_file) {
XContentContainerDevice::Result StfsContainerDevice::LoadHostFiles() {
const XContentContainerHeader* header = GetContainerHeader();
if (header->content_metadata.data_file_count > 0) {
XELOGW("STFS container is not a single file. Loading might fail!");
}
files_.emplace(std::make_pair(0, header_file));
data_ = MappedMemory::Open(host_path_, MappedMemory::Mode::kRead);
if (!data_) {
return Result::kOutOfMemory;
}
return Result::kSuccess;
}
StfsContainerDevice::Result StfsContainerDevice::Read() {
auto& file = files_.at(0);
auto root_entry = new XContentContainerEntry(this, nullptr, "", &files_);
auto root_entry = new StfsContainerEntry(this, nullptr, "", data_.get());
root_entry->attributes_ = kFileAttributeDirectory;
root_entry_ = std::unique_ptr<Entry>(root_entry);
std::vector<XContentContainerEntry*> all_entries;
std::vector<StfsContainerEntry*> all_entries;
// Load all listings.
StfsDirectoryBlock directory;
@ -68,12 +66,7 @@ StfsContainerDevice::Result StfsContainerDevice::Read() {
size_t n = 0;
for (n = 0; n < descriptor.file_table_block_count; n++) {
const size_t offset = BlockToOffset(table_block_index);
xe::filesystem::Seek(file, offset, SEEK_SET);
if (fread(&directory, sizeof(StfsDirectoryBlock), 1, file) != 1) {
XELOGE("ReadSTFS failed to read directory block at 0x{X}", offset);
return Result::kReadError;
}
directory = *reinterpret_cast<StfsDirectoryBlock*>(data_->data() + offset);
for (size_t m = 0; m < kEntriesPerDirectoryBlock; m++) {
const StfsDirectoryEntry& dir_entry = directory.entries[m];
@ -83,13 +76,13 @@ StfsContainerDevice::Result StfsContainerDevice::Read() {
break;
}
XContentContainerEntry* parent_entry =
StfsContainerEntry* parent_entry =
dir_entry.directory_index == 0xFFFF
? root_entry
: all_entries[dir_entry.directory_index];
std::unique_ptr<XContentContainerEntry> entry =
ReadEntry(parent_entry, &files_, &dir_entry);
std::unique_ptr<StfsContainerEntry> entry =
ReadEntry(parent_entry, &dir_entry);
all_entries.push_back(entry.get());
parent_entry->children_.emplace_back(std::move(entry));
}
@ -110,9 +103,8 @@ StfsContainerDevice::Result StfsContainerDevice::Read() {
return Result::kSuccess;
}
std::unique_ptr<XContentContainerEntry> StfsContainerDevice::ReadEntry(
Entry* parent, MultiFileHandles* files,
const StfsDirectoryEntry* dir_entry) {
std::unique_ptr<StfsContainerEntry> StfsContainerDevice::ReadEntry(
Entry* parent, const StfsDirectoryEntry* dir_entry) {
// Filename is stored as Windows-1252, convert it to UTF-8.
std::string ansi_name(reinterpret_cast<const char*>(dir_entry->name),
dir_entry->flags.name_length & 0x3F);
@ -123,7 +115,7 @@ std::unique_ptr<XContentContainerEntry> StfsContainerDevice::ReadEntry(
name = ansi_name;
}
auto entry = XContentContainerEntry::Create(this, parent, name, &files_);
auto entry = StfsContainerEntry::Create(this, parent, name, data_.get());
if (dir_entry->flags.directory) {
entry->attributes_ = kFileAttributeDirectory;
@ -253,15 +245,8 @@ void StfsContainerDevice::UpdateCachedHashTable(
const size_t hash_offset = BlockToHashBlockOffset(block_index, hash_level);
// Do nothing. It's already there.
if (!cached_hash_tables_.count(hash_offset)) {
auto& file = files_.at(0);
xe::filesystem::Seek(file, hash_offset + secondary_table_offset, SEEK_SET);
StfsHashTable table;
if (fread(&table, sizeof(StfsHashTable), 1, file) != 1) {
XELOGE("GetBlockHash failed to read level{} hash table at 0x{:08X}",
hash_level, hash_offset + secondary_table_offset);
return;
}
cached_hash_tables_[hash_offset] = table;
cached_hash_tables_[hash_offset] = *reinterpret_cast<StfsHashTable*>(
data_->data() + hash_offset + secondary_table_offset);
}
uint32_t record = block_index % kBlocksPerHashLevel[0];
@ -283,8 +268,6 @@ void StfsContainerDevice::UpdateCachedHashTables(
}
const StfsHashEntry* StfsContainerDevice::GetBlockHash(uint32_t block_index) {
auto& file = files_.at(0);
const StfsVolumeDescriptor& descriptor =
header_->content_metadata.volume_descriptor.stfs;

View File

@ -10,15 +10,12 @@
#ifndef XENIA_VFS_DEVICES_XCONTENT_DEVICES_STFS_CONTAINER_DEVICE_H_
#define XENIA_VFS_DEVICES_XCONTENT_DEVICES_STFS_CONTAINER_DEVICE_H_
#include <string>
#include <unordered_map>
#include "xenia/base/string_util.h"
#include "xenia/kernel/util/xex2_info.h"
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/stfs_xbox.h"
#include "xenia/vfs/devices/xcontent_container_device.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/stfs_container_entry.h"
namespace xe {
namespace vfs {
@ -61,12 +58,11 @@ class StfsContainerDevice : public XContentContainerDevice {
kBlockSize / sizeof(StfsDirectoryEntry);
void SetupContainer() override;
Result LoadHostFiles(FILE* header_file) override;
Result LoadHostFiles() override;
Result Read() override;
std::unique_ptr<XContentContainerEntry> ReadEntry(
Entry* parent, MultiFileHandles* files,
const StfsDirectoryEntry* dir_entry);
std::unique_ptr<StfsContainerEntry> ReadEntry(
Entry* parent, const StfsDirectoryEntry* dir_entry);
size_t BlockToOffset(uint64_t block_index) const;
uint32_t BlockToHashBlockNumber(uint32_t block_index,
@ -87,6 +83,7 @@ class StfsContainerDevice : public XContentContainerDevice {
uint32_t block_step_[2];
std::unordered_map<size_t, StfsHashTable> cached_hash_tables_;
std::unique_ptr<MappedMemory> data_;
};
} // namespace vfs

View File

@ -0,0 +1,40 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/xcontent_devices/stfs_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/stfs_container_file.h"
namespace xe {
namespace vfs {
StfsContainerEntry::StfsContainerEntry(Device* device, Entry* parent,
const std::string_view path,
MappedMemory* data)
: XContentContainerEntry(device, parent, path), data_(data) {}
StfsContainerEntry::~StfsContainerEntry() = default;
std::unique_ptr<StfsContainerEntry> StfsContainerEntry::Create(
Device* device, Entry* parent, const std::string_view name,
MappedMemory* data) {
auto path = xe::utf8::join_guest_paths(parent->path(), name);
auto entry = std::make_unique<StfsContainerEntry>(device, parent, path, data);
return std::move(entry);
}
X_STATUS StfsContainerEntry::Open(uint32_t desired_access, File** out_file) {
*out_file = new StfsContainerFile(desired_access, this);
return X_STATUS_SUCCESS;
}
bool StfsContainerEntry::DeleteEntryInternal(Entry* entry) { return false; }
} // namespace vfs
} // namespace xe

View File

@ -0,0 +1,42 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_VFS_DEVICES_XCONTENT_STFS_CONTAINER_ENTRY_H_
#define XENIA_VFS_DEVICES_XCONTENT_STFS_CONTAINER_ENTRY_H_
#include "xenia/vfs/devices/xcontent_container_entry.h"
namespace xe {
namespace vfs {
class StfsContainerEntry : public XContentContainerEntry {
public:
StfsContainerEntry(Device* device, Entry* parent, const std::string_view path,
MappedMemory* data);
~StfsContainerEntry() override;
static std::unique_ptr<StfsContainerEntry> Create(Device* device,
Entry* parent,
const std::string_view name,
MappedMemory* data);
MappedMemory* data() const { return data_; }
X_STATUS Open(uint32_t desired_access, File** out_file) override;
private:
bool DeleteEntryInternal(Entry* entry) override;
MappedMemory* data_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_DEVICES_XCONTENT_CONTAINER_ENTRY_H_

View File

@ -0,0 +1,31 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/xcontent_devices/stfs_container_file.h"
#include "xenia/vfs/devices/xcontent_devices/stfs_container_entry.h"
namespace xe {
namespace vfs {
StfsContainerFile::StfsContainerFile(uint32_t file_access,
StfsContainerEntry* entry)
: XContentContainerFile(file_access, entry), entry_(entry) {}
StfsContainerFile::~StfsContainerFile() = default;
void StfsContainerFile::Destroy() { delete this; }
size_t StfsContainerFile::Read(std::span<uint8_t> buffer, size_t offset,
size_t record_file) {
std::memcpy(buffer.data(), entry_->data()->data() + offset, buffer.size());
return buffer.size();
}
} // namespace vfs
} // namespace xe

View File

@ -0,0 +1,48 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_VFS_DEVICES_XCONTENT_STFS_CONTAINER_FILE_H_
#define XENIA_VFS_DEVICES_XCONTENT_STFS_CONTAINER_FILE_H_
#include <span>
#include "xenia/vfs/devices/xcontent_container_file.h"
#include "xenia/vfs/file.h"
#include "xenia/xbox.h"
namespace xe {
namespace vfs {
class StfsContainerEntry;
class StfsContainerFile : public XContentContainerFile {
public:
StfsContainerFile(uint32_t file_access, StfsContainerEntry* entry);
~StfsContainerFile() override;
void Destroy() override;
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override {
return X_STATUS_ACCESS_DENIED;
}
X_STATUS SetLength(size_t length) override { return X_STATUS_ACCESS_DENIED; }
private:
size_t Read(std::span<uint8_t> buffer, size_t offset,
size_t record_file) override;
StfsContainerEntry* entry_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_DEVICES_XCONTENT_CONTAINER_FILE_H_

View File

@ -7,13 +7,9 @@
******************************************************************************
*/
#include <algorithm>
#include <vector>
#include "xenia/base/logging.h"
#include "xenia/vfs/devices/xcontent_container_device.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/svod_container_device.h"
#include "xenia/base/logging.h"
#include "xenia/vfs/devices/xcontent_devices/svod_container_entry.h"
namespace xe {
namespace vfs {
@ -26,10 +22,15 @@ SvodContainerDevice::SvodContainerDevice(const std::string_view mount_path,
SetName("FATX");
}
SvodContainerDevice::~SvodContainerDevice() { CloseFiles(); }
SvodContainerDevice::~SvodContainerDevice() {
for (auto& file : files_) {
fclose(file.second);
}
files_.clear();
files_total_size_ = 0;
}
SvodContainerDevice::Result SvodContainerDevice::LoadHostFiles(
FILE* header_file) {
SvodContainerDevice::Result SvodContainerDevice::LoadHostFiles() {
std::filesystem::path data_fragment_path = host_path_;
data_fragment_path += ".data";
if (!std::filesystem::exists(data_fragment_path)) {
@ -58,7 +59,7 @@ SvodContainerDevice::Result SvodContainerDevice::LoadHostFiles(
auto file = xe::filesystem::OpenFile(path, "rb");
if (!file) {
XELOGI("Failed to map SVOD file {}.", path);
CloseFiles();
// CloseFiles();
return Result::kReadError;
}
@ -100,7 +101,7 @@ XContentContainerDevice::Result SvodContainerDevice::Read() {
const uint64_t root_creation_timestamp =
decode_fat_timestamp(root_data.creation_date, root_data.creation_time);
auto root_entry = new XContentContainerEntry(this, nullptr, "", &files_);
auto root_entry = new SvodContainerEntry(this, nullptr, "", &files_);
root_entry->attributes_ = kFileAttributeDirectory;
root_entry->access_timestamp_ = root_creation_timestamp;
root_entry->create_timestamp_ = root_creation_timestamp;
@ -112,7 +113,7 @@ XContentContainerDevice::Result SvodContainerDevice::Read() {
}
SvodContainerDevice::Result SvodContainerDevice::ReadEntry(
uint32_t block, uint32_t ordinal, XContentContainerEntry* parent) {
uint32_t block, uint32_t ordinal, SvodContainerEntry* parent) {
// For games with a large amount of files, the ordinal offset can overrun
// the current block and potentially hit a hash block.
size_t ordinal_offset = ordinal * 0x4;
@ -184,7 +185,7 @@ SvodContainerDevice::Result SvodContainerDevice::ReadEntry(
// NOTE: SVOD entries don't have timestamps for individual files, which can
// cause issues when decrypting games. Using the root entry's timestamp
// solves this issues.
auto entry = XContentContainerEntry::Create(this, parent, name, &files_);
auto entry = SvodContainerEntry::Create(this, parent, name, &files_);
if (dir_entry.attributes & kFileAttributeDirectory) {
// Entry is a directory
entry->attributes_ = kFileAttributeDirectory | kFileAttributeReadOnly;

View File

@ -11,19 +11,14 @@
#define XENIA_VFS_DEVICES_XCONTENT_DEVICES_SVOD_CONTAINER_DEVICE_H_
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include "xenia/base/string_util.h"
#include "xenia/kernel/util/xex2_info.h"
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/stfs_xbox.h"
#include "xenia/vfs/devices/xcontent_container_device.h"
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/svod_container_entry.h"
namespace xe {
namespace vfs {
class SvodContainerDevice : public XContentContainerDevice {
public:
SvodContainerDevice(const std::string_view mount_path,
@ -49,11 +44,11 @@ class SvodContainerDevice : public XContentContainerDevice {
};
const char* MEDIA_MAGIC = "MICROSOFT*XBOX*MEDIA";
Result LoadHostFiles(FILE* header_file) override;
Result LoadHostFiles() override;
Result Read() override;
Result ReadEntry(uint32_t sector, uint32_t ordinal,
XContentContainerEntry* parent);
SvodContainerEntry* parent);
void BlockToOffset(size_t sector, size_t* address, size_t* file_index) const;
Result SetLayout(FILE* header, size_t& magic_offset);
@ -69,6 +64,7 @@ class SvodContainerDevice : public XContentContainerDevice {
size_t svod_base_offset_;
SvodLayoutType svod_layout_;
MultiFileHandles files_;
};
} // namespace vfs

View File

@ -0,0 +1,41 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/xcontent_devices/svod_container_entry.h"
#include "xenia/vfs/devices/xcontent_devices/svod_container_file.h"
namespace xe {
namespace vfs {
SvodContainerEntry::SvodContainerEntry(Device* device, Entry* parent,
const std::string_view path,
MultiFileHandles* files)
: XContentContainerEntry(device, parent, path), files_(files) {}
SvodContainerEntry::~SvodContainerEntry() = default;
std::unique_ptr<SvodContainerEntry> SvodContainerEntry::Create(
Device* device, Entry* parent, const std::string_view name,
MultiFileHandles* files) {
auto path = xe::utf8::join_guest_paths(parent->path(), name);
auto entry =
std::make_unique<SvodContainerEntry>(device, parent, path, files);
return std::move(entry);
}
X_STATUS SvodContainerEntry::Open(uint32_t desired_access, File** out_file) {
*out_file = new SvodContainerFile(desired_access, this);
return X_STATUS_SUCCESS;
}
bool SvodContainerEntry::DeleteEntryInternal(Entry* entry) { return false; }
} // namespace vfs
} // namespace xe

View File

@ -0,0 +1,48 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_ENTRY_H_
#define XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_ENTRY_H_
#include <map>
#include "xenia/vfs/devices/xcontent_container_entry.h"
#include "xenia/vfs/file.h"
namespace xe {
namespace vfs {
typedef std::map<size_t, FILE*> MultiFileHandles;
class XContentContainerDevice;
class SvodContainerEntry : public XContentContainerEntry {
public:
SvodContainerEntry(Device* device, Entry* parent, const std::string_view path,
MultiFileHandles* files);
~SvodContainerEntry() override;
static std::unique_ptr<SvodContainerEntry> Create(Device* device,
Entry* parent,
const std::string_view name,
MultiFileHandles* files);
MultiFileHandles* files() const { return files_; }
X_STATUS Open(uint32_t desired_access, File** out_file) override;
private:
bool DeleteEntryInternal(Entry* entry) override;
MultiFileHandles* files_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_ENTRY_H_

View File

@ -0,0 +1,32 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/vfs/devices/xcontent_devices/svod_container_file.h"
#include "xenia/vfs/devices/xcontent_devices/svod_container_entry.h"
namespace xe {
namespace vfs {
SvodContainerFile::SvodContainerFile(uint32_t file_access,
SvodContainerEntry* entry)
: XContentContainerFile(file_access, entry), entry_(entry) {}
SvodContainerFile::~SvodContainerFile() = default;
void SvodContainerFile::Destroy() { delete this; }
size_t SvodContainerFile::Read(std::span<uint8_t> buffer, size_t offset,
size_t record_file) {
auto& file = entry_->files()->at(record_file);
xe::filesystem::Seek(file, offset, SEEK_SET);
return fread(buffer.data(), 1, buffer.size(), file);
}
} // namespace vfs
} // namespace xe

View File

@ -0,0 +1,43 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2025 Xenia Canary. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_FILE_H_
#define XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_FILE_H_
#include "xenia/vfs/devices/xcontent_container_file.h"
namespace xe {
namespace vfs {
class SvodContainerEntry;
class SvodContainerFile : public XContentContainerFile {
public:
SvodContainerFile(uint32_t file_access, SvodContainerEntry* entry);
~SvodContainerFile() override;
void Destroy() override;
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) override {
return X_STATUS_ACCESS_DENIED;
}
X_STATUS SetLength(size_t length) override { return X_STATUS_ACCESS_DENIED; }
private:
size_t Read(std::span<uint8_t> buffer, size_t offset,
size_t record_file) override;
SvodContainerEntry* entry_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_DEVICES_XCONTENT_SVOD_CONTAINER_FILE_H_

View File

@ -11,6 +11,7 @@
#define XENIA_VFS_FILE_H_
#include <cstdint>
#include <span>
#include "xenia/xbox.h"
@ -27,20 +28,20 @@ class File {
virtual void Destroy() = 0;
virtual X_STATUS ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) = 0;
virtual X_STATUS WriteSync(const void* buffer, size_t buffer_length,
virtual X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) = 0;
virtual X_STATUS WriteSync(std::span<const uint8_t> buffer,
size_t byte_offset, size_t* out_bytes_written) = 0;
// TODO: Parameters
virtual X_STATUS ReadAsync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {
virtual X_STATUS ReadAsync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_read) {
return X_STATUS_NOT_IMPLEMENTED;
}
// TODO: Parameters
virtual X_STATUS WriteAsync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_written) {
virtual X_STATUS WriteAsync(std::span<uint8_t> buffer, size_t byte_offset,
size_t* out_bytes_written) {
return X_STATUS_NOT_IMPLEMENTED;
}

View File

@ -391,7 +391,8 @@ X_STATUS VirtualFileSystem::ExtractContentFile(Entry* entry,
while (remaining_size > 0) {
size_t bytes_read = 0;
in_file->ReadSync(buffer, write_buffer_size, offset, &bytes_read);
in_file->ReadSync(std::span<uint8_t>(buffer, write_buffer_size), offset,
&bytes_read);
fwrite(buffer, bytes_read, 1, file);
offset += bytes_read;
remaining_size -= bytes_read;