Refactoring vfs to remove a lot of duplicated code.

Progress on #294.
This commit is contained in:
Ben Vanik 2015-06-27 22:37:49 -07:00
parent 1ac19f1b08
commit 83872d8e8f
37 changed files with 877 additions and 1366 deletions

View File

@ -205,8 +205,6 @@
<ClCompile Include="src\xenia\vfs\devices\stfs_container_file.cc" />
<ClCompile Include="src\xenia\vfs\entry.cc" />
<ClCompile Include="src\xenia\vfs\virtual_file_system.cc" />
<ClCompile Include="src\xenia\vfs\gdfx.cc" />
<ClCompile Include="src\xenia\vfs\stfs.cc" />
<ClCompile Include="third_party\beaengine\beaengineSources\BeaEngine.c" />
<ClCompile Include="third_party\capstone\arch\X86\X86Disassembler.c">
<CompileAs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">CompileAsCpp</CompileAs>
@ -460,8 +458,6 @@
<ClInclude Include="src\xenia\vfs\devices\stfs_container_file.h" />
<ClInclude Include="src\xenia\vfs\entry.h" />
<ClInclude Include="src\xenia\vfs\virtual_file_system.h" />
<ClInclude Include="src\xenia\vfs\gdfx.h" />
<ClInclude Include="src\xenia\vfs\stfs.h" />
<ClInclude Include="src\xenia\xbox.h" />
<ClInclude Include="third_party\beaengine\include\beaengine\basic_types.h" />
<ClInclude Include="third_party\beaengine\include\beaengine\BeaEngine.h" />

View File

@ -745,12 +745,6 @@
<ClCompile Include="src\xenia\vfs\entry.cc">
<Filter>src\xenia\vfs</Filter>
</ClCompile>
<ClCompile Include="src\xenia\vfs\gdfx.cc">
<Filter>src\xenia\vfs</Filter>
</ClCompile>
<ClCompile Include="src\xenia\vfs\stfs.cc">
<Filter>src\xenia\vfs</Filter>
</ClCompile>
<ClCompile Include="src\xenia\vfs\devices\disc_image_device.cc">
<Filter>src\xenia\vfs\devices</Filter>
</ClCompile>
@ -1452,12 +1446,6 @@
<ClInclude Include="src\xenia\vfs\entry.h">
<Filter>src\xenia\vfs</Filter>
</ClInclude>
<ClInclude Include="src\xenia\vfs\gdfx.h">
<Filter>src\xenia\vfs</Filter>
</ClInclude>
<ClInclude Include="src\xenia\vfs\stfs.h">
<Filter>src\xenia\vfs</Filter>
</ClInclude>
<ClInclude Include="src\xenia\vfs\devices\disc_image_device.h">
<Filter>src\xenia\vfs\devices</Filter>
</ClInclude>

View File

@ -33,6 +33,9 @@ struct FileInfo {
Type type;
std::wstring name;
size_t total_size;
uint64_t create_timestamp;
uint64_t access_timestamp;
uint64_t write_timestamp;
};
std::vector<FileInfo> ListFiles(const std::wstring& path);

View File

@ -43,6 +43,8 @@ bool DeleteFolder(const std::wstring& path) {
return SHFileOperation(&op) == 0;
}
#define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime)
std::vector<FileInfo> ListFiles(const std::wstring& path) {
std::vector<FileInfo> result;
@ -66,6 +68,9 @@ std::vector<FileInfo> ListFiles(const std::wstring& path) {
(ffd.nFileSizeHigh * (size_t(MAXDWORD) + 1)) + ffd.nFileSizeLow;
}
info.name = ffd.cFileName;
info.create_timestamp = COMBINE_TIME(ffd.ftCreationTime);
info.access_timestamp = COMBINE_TIME(ffd.ftLastAccessTime);
info.write_timestamp = COMBINE_TIME(ffd.ftLastWriteTime);
result.push_back(info);
} while (FindNextFile(handle, &ffd) != 0);
FindClose(handle);

View File

@ -209,6 +209,10 @@ X_STATUS Emulator::LaunchXexFile(std::wstring path) {
auto parent_path = xe::find_base_path(path);
auto device =
std::make_unique<vfs::HostPathDevice>(mount_path, parent_path, true);
if (!device->Initialize()) {
XELOGE("Unable to scan host path");
return X_STATUS_NO_SUCH_FILE;
}
if (!file_system_->RegisterDevice(std::move(device))) {
XELOGE("Unable to register host path");
return X_STATUS_NO_SUCH_FILE;

View File

@ -9,14 +9,19 @@
#include "xenia/kernel/objects/xfile.h"
#include "xenia/base/math.h"
#include "xenia/kernel/async_request.h"
#include "xenia/kernel/objects/xevent.h"
namespace xe {
namespace kernel {
XFile::XFile(KernelState* kernel_state, vfs::Mode mode)
: mode_(mode), position_(0), XObject(kernel_state, kTypeFile) {
XFile::XFile(KernelState* kernel_state, vfs::Mode mode, vfs::Entry* entry)
: XObject(kernel_state, kTypeFile),
entry_(entry),
mode_(mode),
position_(0),
find_index_(0) {
async_event_ = new XEvent(kernel_state);
async_event_->Initialize(false, false);
}
@ -29,6 +34,58 @@ XFile::~XFile() {
void* XFile::GetWaitHandle() { return async_event_->GetWaitHandle(); }
X_STATUS XFile::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) {
assert_not_null(out_info);
vfs::Entry* entry = nullptr;
if (file_name != nullptr) {
// Only queries in the current directory are supported for now.
assert_true(std::strchr(file_name, '\\') == nullptr);
find_engine_.SetRule(file_name);
// Always restart the search?
find_index_ = 0;
entry = entry_->IterateChildren(find_engine_, &find_index_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
} else {
if (restart) {
find_index_ = 0;
}
entry = entry_->IterateChildren(find_engine_, &find_index_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
}
auto end = (uint8_t*)out_info + length;
const auto& entry_name = entry->name();
if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) {
assert_always("Buffer overflow?");
return X_STATUS_NO_SUCH_FILE;
}
out_info->next_entry_offset = 0;
out_info->file_index = static_cast<uint32_t>(find_index_);
out_info->creation_time = entry->create_timestamp();
out_info->last_access_time = entry->access_timestamp();
out_info->last_write_time = entry->write_timestamp();
out_info->change_time = entry->write_timestamp();
out_info->end_of_file = entry->size();
out_info->allocation_size = entry->allocation_size();
out_info->attributes = entry->attributes();
out_info->file_name_length = static_cast<uint32_t>(entry_name.size());
std::memcpy(out_info->file_name, entry_name.data(), entry_name.size());
return X_STATUS_SUCCESS;
}
X_STATUS XFile::Read(void* buffer, size_t buffer_length, size_t byte_offset,
size_t* out_bytes_read) {
if (byte_offset == -1) {

View File

@ -10,7 +10,9 @@
#ifndef XENIA_KERNEL_XBOXKRNL_XFILE_H_
#define XENIA_KERNEL_XBOXKRNL_XFILE_H_
#include "xenia/base/filesystem.h"
#include "xenia/kernel/xobject.h"
#include "xenia/vfs/device.h"
#include "xenia/vfs/entry.h"
#include "xenia/xbox.h"
@ -29,7 +31,7 @@ struct X_FILE_NETWORK_OPEN_INFORMATION {
xe::be<uint64_t> change_time;
xe::be<uint64_t> allocation_size;
xe::be<uint64_t> end_of_file; // size in bytes
xe::be<X_FILE_ATTRIBUTES> attributes;
xe::be<uint32_t> attributes;
xe::be<uint32_t> pad;
};
@ -45,7 +47,7 @@ class X_FILE_DIRECTORY_INFORMATION {
uint64_t change_time;
uint64_t end_of_file; // size in bytes
uint64_t allocation_size;
X_FILE_ATTRIBUTES attributes;
uint32_t attributes; // X_FILE_ATTRIBUTES
uint32_t file_name_length;
char file_name[1];
@ -75,20 +77,19 @@ static_assert_size(X_FILE_DIRECTORY_INFORMATION, 72);
class XFile : public XObject {
public:
virtual ~XFile();
~XFile() override;
virtual const std::string& path() const = 0;
virtual const std::string& name() const = 0;
vfs::Device* device() const { return entry_->device(); }
vfs::Entry* entry() const { return entry_; }
virtual vfs::Device* device() const = 0;
const std::string& path() const { return entry_->path(); }
const std::string& name() const { return entry_->name(); }
size_t position() const { return position_; }
void set_position(size_t value) { position_ = value; }
virtual X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) = 0;
virtual X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) = 0;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart);
X_STATUS Read(void* buffer, size_t buffer_length, size_t byte_offset,
size_t* out_bytes_read);
@ -101,7 +102,7 @@ class XFile : public XObject {
virtual void* GetWaitHandle();
protected:
XFile(KernelState* kernel_state, vfs::Mode mode);
XFile(KernelState* kernel_state, vfs::Mode mode, vfs::Entry* entry);
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,
@ -110,12 +111,16 @@ class XFile : public XObject {
}
private:
vfs::Entry* entry_;
vfs::Mode mode_;
XEvent* async_event_;
// TODO(benvanik): create flags, open state, etc.
size_t position_;
xe::filesystem::WildcardEngine find_engine_;
size_t find_index_;
};
} // namespace kernel

View File

@ -59,18 +59,11 @@ X_STATUS XUserModule::LoadFromFile(std::string path) {
// Load the module.
result = LoadFromMemory(mmap->data(), mmap->size());
} else {
X_FILE_NETWORK_OPEN_INFORMATION file_info = {0};
result = fs_entry->QueryInfo(&file_info);
if (result) {
return result;
}
std::vector<uint8_t> buffer(file_info.end_of_file);
std::vector<uint8_t> buffer(fs_entry->size());
// Open file for reading.
XFile* file_ptr = nullptr;
result = kernel_state()->file_system()->Open(
std::move(fs_entry), kernel_state(), vfs::Mode::READ, false, &file_ptr);
result = fs_entry->Open(kernel_state(), vfs::Mode::READ, false, &file_ptr);
object_ref<XFile> file(file_ptr);
if (result) {
return result;

View File

@ -187,7 +187,7 @@ X_STATUS NtCreateFile(PPCContext* ppc_context, KernelState* kernel_state,
uint32_t handle;
auto fs = kernel_state->file_system();
std::unique_ptr<Entry> entry;
Entry* entry;
object_ref<XFile> root_file;
if (object_attrs->root_directory != 0xFFFFFFFD && // ObDosDevices
@ -233,9 +233,9 @@ X_STATUS NtCreateFile(PPCContext* ppc_context, KernelState* kernel_state,
mode = vfs::Mode::READ;
}
XFile* file_ptr = nullptr;
result = fs->Open(std::move(entry), kernel_state, mode,
false, // TODO(benvanik): pick async mode, if needed.
&file_ptr);
result = entry->Open(kernel_state, mode,
false, // TODO(benvanik): pick async mode, if needed.
&file_ptr);
file = object_ref<XFile>(file_ptr);
}
@ -618,10 +618,14 @@ dword_result_t NtQueryInformationFile(
assert_true(length == 56);
auto file_info = file_info_ptr.as<X_FILE_NETWORK_OPEN_INFORMATION*>();
result = file->QueryInfo(file_info);
if (XSUCCEEDED(result)) {
info = 56;
}
file_info->creation_time = file->entry()->create_timestamp();
file_info->last_access_time = file->entry()->access_timestamp();
file_info->last_write_time = file->entry()->write_timestamp();
file_info->change_time = file->entry()->write_timestamp();
file_info->allocation_size = file->entry()->allocation_size();
file_info->end_of_file = file->entry()->size();
file_info->attributes = file->entry()->attributes();
info = 56;
break;
}
case XFileXctdCompressionInformation:
@ -701,10 +705,19 @@ SHIM_CALL NtQueryFullAttributesFile_shim(PPCContext* ppc_context,
auto entry = fs->ResolvePath(object_name);
if (entry) {
// Found.
result = X_STATUS_SUCCESS;
auto file_info =
kernel_memory()->TranslateVirtual<X_FILE_NETWORK_OPEN_INFORMATION*>(
file_info_ptr);
result = entry->QueryInfo(file_info);
file_info->creation_time = entry->create_timestamp();
file_info->last_access_time = entry->access_timestamp();
file_info->last_write_time = entry->write_timestamp();
file_info->change_time = entry->write_timestamp();
file_info->allocation_size = entry->allocation_size();
file_info->end_of_file = entry->size();
file_info->attributes = entry->attributes();
}
free(object_name);

View File

@ -9,6 +9,8 @@
#include "xenia/vfs/device.h"
#include "xenia/base/logging.h"
namespace xe {
namespace vfs {
@ -16,5 +18,31 @@ Device::Device(const std::string& mount_path) : mount_path_(mount_path) {}
Device::~Device() = default;
void Device::Dump(StringBuffer* string_buffer) {
if (root_entry_) {
root_entry_->Dump(string_buffer, 0);
}
}
Entry* Device::ResolvePath(const char* path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS("Device::ResolvePath(%s)", path);
// Walk the path, one separator at a time.
auto entry = root_entry_.get();
auto path_parts = xe::split_path(path);
for (auto& part : path_parts) {
entry = entry->GetChild(part);
if (!entry) {
// Not found.
return nullptr;
}
}
return entry;
}
} // namespace vfs
} // namespace xe

View File

@ -13,6 +13,7 @@
#include <memory>
#include <string>
#include "xenia/base/string_buffer.h"
#include "xenia/vfs/entry.h"
namespace xe {
@ -23,11 +24,14 @@ class Device {
Device(const std::string& path);
virtual ~Device();
virtual bool Initialize() = 0;
void Dump(StringBuffer* string_buffer);
const std::string& mount_path() const { return mount_path_; }
virtual bool is_read_only() const { return true; }
virtual std::unique_ptr<Entry> ResolvePath(const char* path) = 0;
Entry* ResolvePath(const char* path);
virtual uint32_t total_allocation_units() const = 0;
virtual uint32_t available_allocation_units() const = 0;
@ -36,6 +40,7 @@ class Device {
protected:
std::string mount_path_;
std::unique_ptr<Entry> root_entry_;
};
} // namespace vfs

View File

@ -11,17 +11,18 @@
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/vfs/gdfx.h"
#include "xenia/vfs/devices/disc_image_entry.h"
namespace xe {
namespace vfs {
const size_t kXESectorSize = 2048;
DiscImageDevice::DiscImageDevice(const std::string& mount_path,
const std::wstring& local_path)
: Device(mount_path), local_path_(local_path), gdfx_(nullptr) {}
: Device(mount_path), local_path_(local_path) {}
DiscImageDevice::~DiscImageDevice() { delete gdfx_; }
DiscImageDevice::~DiscImageDevice() = default;
bool DiscImageDevice::Initialize() {
mmap_ = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
@ -30,38 +31,133 @@ bool DiscImageDevice::Initialize() {
return false;
}
gdfx_ = new GDFX(mmap_.get());
GDFX::Error error = gdfx_->Load();
if (error != GDFX::kSuccess) {
XELOGE("GDFX init failed: %d", error);
ParseState state = {0};
state.ptr = mmap_->data();
state.size = mmap_->size();
auto result = Verify(state);
if (result != Error::kSuccess) {
XELOGE("Failed to verify disc image header: %d", result);
return false;
}
// gdfx_->Dump();
result = ReadAllEntries(state, state.ptr + state.root_offset);
if (result != Error::kSuccess) {
XELOGE("Failed to read all GDFX entries: %d", result);
return false;
}
return true;
}
std::unique_ptr<Entry> DiscImageDevice::ResolvePath(const char* path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS("DiscImageDevice::ResolvePath(%s)", path);
GDFXEntry* gdfx_entry = gdfx_->root_entry();
// Walk the path, one separator at a time.
auto path_parts = xe::split_path(path);
for (auto& part : path_parts) {
gdfx_entry = gdfx_entry->GetChild(part.c_str());
if (!gdfx_entry) {
// Not found.
return nullptr;
DiscImageDevice::Error DiscImageDevice::Verify(ParseState& state) {
// Find sector 32 of the game partition - try at a few points.
const static size_t likely_offsets[] = {
0x00000000, 0x0000FB20, 0x00020600, 0x0FD90000,
};
bool magic_found = false;
for (size_t n = 0; n < xe::countof(likely_offsets); n++) {
state.game_offset = likely_offsets[n];
if (VerifyMagic(state, state.game_offset + (32 * kXESectorSize))) {
magic_found = true;
break;
}
}
if (!magic_found) {
// File doesn't have the magic values - likely not a real GDFX source.
return Error::kErrorFileMismatch;
}
return std::make_unique<DiscImageEntry>(this, path, mmap_.get(), gdfx_entry);
// Read sector 32 to get FS state.
if (state.size < state.game_offset + (32 * kXESectorSize)) {
return Error::kErrorReadError;
}
uint8_t* fs_ptr = state.ptr + state.game_offset + (32 * kXESectorSize);
state.root_sector = xe::load<uint32_t>(fs_ptr + 20);
state.root_size = xe::load<uint32_t>(fs_ptr + 24);
state.root_offset = state.game_offset + (state.root_sector * kXESectorSize);
if (state.root_size < 13 || state.root_size > 32 * 1024 * 1024) {
return Error::kErrorDamagedFile;
}
return Error::kSuccess;
}
bool DiscImageDevice::VerifyMagic(ParseState& state, size_t offset) {
// Simple check to see if the given offset contains the magic value.
return std::memcmp(state.ptr + offset, "MICROSOFT*XBOX*MEDIA", 20) == 0;
}
DiscImageDevice::Error DiscImageDevice::ReadAllEntries(
ParseState& state, const uint8_t* root_buffer) {
auto root_entry = new DiscImageEntry(this, "", mmap_.get());
root_entry->attributes_ = kFileAttributeDirectory;
root_entry_ = std::unique_ptr<Entry>(root_entry);
if (!ReadEntry(state, root_buffer, 0, root_entry)) {
return Error::kErrorOutOfMemory;
}
return Error::kSuccess;
}
bool DiscImageDevice::ReadEntry(ParseState& state, const uint8_t* buffer,
uint16_t entry_ordinal,
DiscImageEntry* parent) {
const uint8_t* p = buffer + (entry_ordinal * 4);
uint16_t node_l = xe::load<uint16_t>(p + 0);
uint16_t node_r = xe::load<uint16_t>(p + 2);
size_t sector = xe::load<uint32_t>(p + 4);
size_t length = xe::load<uint32_t>(p + 8);
uint8_t attributes = xe::load<uint8_t>(p + 12);
uint8_t name_length = xe::load<uint8_t>(p + 13);
char* name = (char*)(p + 14);
if (node_l && !ReadEntry(state, buffer, node_l, parent)) {
return false;
}
auto entry =
new DiscImageEntry(this, std::string(name, name_length), mmap_.get());
entry->attributes_ = attributes | kFileAttributeReadOnly;
entry->size_ = length;
entry->allocation_size_ = xe::round_up(length, bytes_per_sector());
entry->create_timestamp_ = 0;
entry->access_timestamp_ = 0;
entry->write_timestamp_ = 0;
// Add to parent.
parent->children_.emplace_back(std::unique_ptr<Entry>(entry));
if (attributes & kFileAttributeDirectory) {
// Folder.
entry->data_offset_ = 0;
entry->data_size_ = 0;
if (length) {
// Not a leaf - read in children.
if (state.size < state.game_offset + (sector * kXESectorSize)) {
// Out of bounds read.
return false;
}
// Read child list.
uint8_t* folder_ptr =
state.ptr + state.game_offset + (sector * kXESectorSize);
if (!ReadEntry(state, folder_ptr, 0, entry)) {
return false;
}
}
} else {
// File.
entry->data_offset_ = state.game_offset + (sector * kXESectorSize);
entry->data_size_ = length;
}
// Read next file in the list.
if (node_r && !ReadEntry(state, buffer, node_r, parent)) {
return false;
}
return true;
}
} // namespace vfs

View File

@ -19,7 +19,7 @@
namespace xe {
namespace vfs {
class GDFX;
class DiscImageEntry;
class DiscImageDevice : public Device {
public:
@ -27,9 +27,7 @@ class DiscImageDevice : public Device {
const std::wstring& local_path);
~DiscImageDevice() override;
bool Initialize();
std::unique_ptr<Entry> ResolvePath(const char* path) override;
bool Initialize() override;
uint32_t total_allocation_units() const override {
return uint32_t(mmap_->size() / sectors_per_allocation_unit() /
@ -40,9 +38,31 @@ class DiscImageDevice : public Device {
uint32_t bytes_per_sector() const override { return 2 * 1024; }
private:
enum class Error {
kSuccess = 0,
kErrorOutOfMemory = -1,
kErrorReadError = -10,
kErrorFileMismatch = -30,
kErrorDamagedFile = -31,
};
std::wstring local_path_;
std::unique_ptr<MappedMemory> mmap_;
GDFX* gdfx_;
typedef struct {
uint8_t* ptr;
size_t size; // Size (bytes) of total image.
size_t game_offset; // Offset (bytes) of game partition.
size_t root_sector; // Offset (sector) of root.
size_t root_offset; // Offset (bytes) of root.
size_t root_size; // Size (bytes) of root.
} ParseState;
Error Verify(ParseState& state);
bool VerifyMagic(ParseState& state, size_t offset);
Error ReadAllEntries(ParseState& state, const uint8_t* root_buffer);
bool ReadEntry(ParseState& state, const uint8_t* buffer,
uint16_t entry_ordinal, DiscImageEntry* parent);
};
} // namespace vfs

View File

@ -17,77 +17,11 @@
namespace xe {
namespace vfs {
DiscImageEntry::DiscImageEntry(Device* device, const char* path,
MappedMemory* mmap, GDFXEntry* gdfx_entry)
: Entry(device, path),
mmap_(mmap),
gdfx_entry_(gdfx_entry),
it_(gdfx_entry->children.begin()) {}
DiscImageEntry::DiscImageEntry(Device* device, std::string path,
MappedMemory* mmap)
: Entry(device, path), mmap_(mmap), data_offset_(0), data_size_(0) {}
DiscImageEntry::~DiscImageEntry() {}
X_STATUS DiscImageEntry::QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
assert_not_null(out_info);
out_info->creation_time = 0;
out_info->last_access_time = 0;
out_info->last_write_time = 0;
out_info->change_time = 0;
out_info->allocation_size = xe::round_up(gdfx_entry_->size, 2048);
out_info->end_of_file = gdfx_entry_->size;
out_info->attributes = gdfx_entry_->attributes;
return X_STATUS_SUCCESS;
}
X_STATUS DiscImageEntry::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) {
assert_not_null(out_info);
GDFXEntry* entry(nullptr);
if (file_name != nullptr) {
// Only queries in the current directory are supported for now
assert_true(std::strchr(file_name, '\\') == nullptr);
find_engine_.SetRule(file_name);
// Always restart the search?
it_ = gdfx_entry_->children.begin();
entry = gdfx_entry_->GetChild(find_engine_, it_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
} else {
if (restart) {
it_ = gdfx_entry_->children.begin();
}
entry = gdfx_entry_->GetChild(find_engine_, it_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
auto end = (uint8_t*)out_info + length;
auto entry_name = entry->name;
if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) {
return X_STATUS_NO_SUCH_FILE;
}
}
out_info->next_entry_offset = 0;
out_info->file_index = 0xCDCDCDCD;
out_info->creation_time = 0;
out_info->last_access_time = 0;
out_info->last_write_time = 0;
out_info->change_time = 0;
out_info->end_of_file = entry->size;
out_info->allocation_size = xe::round_up(entry->size, 2048);
out_info->attributes = entry->attributes;
out_info->file_name_length = static_cast<uint32_t>(entry->name.size());
memcpy(out_info->file_name, entry->name.c_str(), entry->name.size());
return X_STATUS_SUCCESS;
}
DiscImageEntry::~DiscImageEntry() = default;
X_STATUS DiscImageEntry::Open(KernelState* kernel_state, Mode mode, bool async,
XFile** out_file) {
@ -102,9 +36,8 @@ std::unique_ptr<MappedMemory> DiscImageEntry::OpenMapped(
return nullptr;
}
size_t real_offset = gdfx_entry_->offset + offset;
size_t real_length =
length ? std::min(length, gdfx_entry_->size) : gdfx_entry_->size;
size_t real_offset = data_offset_ + offset;
size_t real_length = length ? std::min(length, data_size_) : data_size_;
return mmap_->Slice(mode, real_offset, real_length);
}

View File

@ -15,23 +15,20 @@
#include "xenia/base/filesystem.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/entry.h"
#include "xenia/vfs/gdfx.h"
namespace xe {
namespace vfs {
class DiscImageDevice;
class DiscImageEntry : public Entry {
public:
DiscImageEntry(Device* device, const char* path, MappedMemory* mmap,
GDFXEntry* gdfx_entry);
DiscImageEntry(Device* device, std::string path, MappedMemory* mmap);
~DiscImageEntry() override;
MappedMemory* mmap() const { return mmap_; }
GDFXEntry* gdfx_entry() const { return gdfx_entry_; }
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; }
X_STATUS Open(KernelState* kernel_state, Mode mode, bool async,
XFile** out_file) override;
@ -42,11 +39,11 @@ class DiscImageEntry : public Entry {
size_t length) override;
private:
MappedMemory* mmap_;
GDFXEntry* gdfx_entry_;
friend class DiscImageDevice;
xe::filesystem::WildcardEngine find_engine_;
GDFXEntry::child_it_t it_;
MappedMemory* mmap_;
size_t data_offset_;
size_t data_size_;
};
} // namespace vfs

View File

@ -11,43 +11,25 @@
#include <algorithm>
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/disc_image_entry.h"
#include "xenia/vfs/gdfx.h"
namespace xe {
namespace vfs {
DiscImageFile::DiscImageFile(KernelState* kernel_state, Mode mode,
DiscImageEntry* entry)
: XFile(kernel_state, mode), entry_(entry) {}
: XFile(kernel_state, mode, entry), entry_(entry) {}
DiscImageFile::~DiscImageFile() { delete entry_; }
const std::string& DiscImageFile::path() const { return entry_->path(); }
const std::string& DiscImageFile::name() const { return entry_->name(); }
Device* DiscImageFile::device() const { return entry_->device(); }
X_STATUS DiscImageFile::QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
return entry_->QueryInfo(out_info);
}
X_STATUS DiscImageFile::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) {
return entry_->QueryDirectory(out_info, length, file_name, restart);
}
DiscImageFile::~DiscImageFile() = default;
X_STATUS DiscImageFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {
GDFXEntry* gdfx_entry = entry_->gdfx_entry();
if (byte_offset >= gdfx_entry->size) {
if (byte_offset >= entry_->size()) {
return X_STATUS_END_OF_FILE;
}
size_t real_offset = gdfx_entry->offset + byte_offset;
size_t real_length = std::min(buffer_length, gdfx_entry->size - byte_offset);
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);
*out_bytes_read = real_length;
return X_STATUS_SUCCESS;

View File

@ -23,15 +23,6 @@ class DiscImageFile : public XFile {
DiscImageEntry* entry);
~DiscImageFile() override;
const std::string& path() const override;
const std::string& name() const override;
Device* device() const override;
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
protected:
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
size_t* out_bytes_read) override;

View File

@ -11,6 +11,7 @@
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/vfs/devices/host_path_entry.h"
#include "xenia/kernel/objects/xfile.h"
@ -21,30 +22,50 @@ HostPathDevice::HostPathDevice(const std::string& mount_path,
const std::wstring& local_path, bool read_only)
: Device(mount_path), local_path_(local_path), read_only_(read_only) {}
HostPathDevice::~HostPathDevice() {}
HostPathDevice::~HostPathDevice() = default;
std::unique_ptr<Entry> HostPathDevice::ResolvePath(const char* path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
XELOGFS("HostPathDevice::ResolvePath(%s)", path);
auto rel_path = xe::to_wstring(path);
auto full_path = xe::join_paths(local_path_, rel_path);
full_path = xe::fix_path_separators(full_path);
// Only check the file exists when the device is read-only
if (read_only_) {
if (!xe::filesystem::PathExists(full_path)) {
return nullptr;
}
bool HostPathDevice::Initialize() {
if (!filesystem::PathExists(local_path_)) {
XELOGE("Host path does not exist");
return false;
}
// TODO(benvanik): get file info
// TODO(benvanik): switch based on type
auto root_entry = new HostPathEntry(this, "", local_path_);
root_entry->attributes_ = kFileAttributeDirectory;
root_entry_ = std::unique_ptr<Entry>(root_entry);
PopulateEntry(root_entry);
return std::make_unique<HostPathEntry>(this, path, full_path);
return true;
}
void HostPathDevice::PopulateEntry(HostPathEntry* entry) {
auto child_infos = filesystem::ListFiles(entry->local_path());
for (auto& child_info : child_infos) {
auto child =
new HostPathEntry(this, xe::to_string(child_info.name),
xe::join_paths(entry->local_path(), child_info.name));
child->create_timestamp_ = child_info.create_timestamp;
child->access_timestamp_ = child_info.access_timestamp;
child->write_timestamp_ = child_info.write_timestamp;
if (child_info.type == filesystem::FileInfo::Type::kDirectory) {
child->attributes_ = kFileAttributeDirectory;
} else {
child->attributes_ = kFileAttributeNormal;
if (read_only_) {
child->attributes_ |= kFileAttributeReadOnly;
}
child->size_ = child_info.total_size;
child->allocation_size_ =
xe::round_up(child_info.total_size, bytes_per_sector());
}
entry->children_.push_back(std::unique_ptr<Entry>(child));
if (child_info.type == filesystem::FileInfo::Type::kDirectory) {
PopulateEntry(child);
}
}
}
} // namespace vfs

View File

@ -17,15 +17,17 @@
namespace xe {
namespace vfs {
class HostPathEntry;
class HostPathDevice : public Device {
public:
HostPathDevice(const std::string& mount_path, const std::wstring& local_path,
bool read_only);
~HostPathDevice() override;
bool is_read_only() const { return read_only_; }
bool Initialize() override;
std::unique_ptr<Entry> ResolvePath(const char* path) override;
bool is_read_only() const { return read_only_; }
uint32_t total_allocation_units() const override { return 128 * 1024; }
uint32_t available_allocation_units() const override { return 128 * 1024; }
@ -33,6 +35,8 @@ class HostPathDevice : public Device {
uint32_t bytes_per_sector() const override { return 2 * 1024; }
private:
void PopulateEntry(HostPathEntry* entry);
std::wstring local_path_;
bool read_only_;
};

View File

@ -17,104 +17,11 @@
namespace xe {
namespace vfs {
HostPathEntry::HostPathEntry(Device* device, const char* path,
HostPathEntry::HostPathEntry(Device* device, std::string path,
const std::wstring& local_path)
: Entry(device, path),
local_path_(local_path),
find_file_(INVALID_HANDLE_VALUE) {}
: Entry(device, path), local_path_(local_path) {}
HostPathEntry::~HostPathEntry() {
if (find_file_ != INVALID_HANDLE_VALUE) {
FindClose(find_file_);
}
}
#define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime)
X_STATUS HostPathEntry::QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
assert_not_null(out_info);
WIN32_FILE_ATTRIBUTE_DATA data;
if (!GetFileAttributesEx(local_path_.c_str(), GetFileExInfoStandard, &data)) {
return X_STATUS_ACCESS_DENIED;
}
uint64_t file_size = ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
out_info->creation_time = COMBINE_TIME(data.ftCreationTime);
out_info->last_access_time = COMBINE_TIME(data.ftLastAccessTime);
out_info->last_write_time = COMBINE_TIME(data.ftLastWriteTime);
out_info->change_time = COMBINE_TIME(data.ftLastWriteTime);
out_info->allocation_size = xe::round_up(file_size, 4096);
out_info->end_of_file = file_size;
out_info->attributes = (X_FILE_ATTRIBUTES)data.dwFileAttributes;
return X_STATUS_SUCCESS;
}
X_STATUS HostPathEntry::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) {
assert_not_null(out_info);
WIN32_FIND_DATA ffd;
HANDLE handle = find_file_;
if (restart == true && handle != INVALID_HANDLE_VALUE) {
FindClose(find_file_);
handle = find_file_ = INVALID_HANDLE_VALUE;
}
if (handle == INVALID_HANDLE_VALUE) {
std::wstring target_path = local_path_;
if (!file_name) {
target_path = xe::join_paths(target_path, L"*");
} else {
target_path = xe::join_paths(target_path, xe::to_wstring(file_name));
}
handle = find_file_ = FindFirstFile(target_path.c_str(), &ffd);
if (handle == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return X_STATUS_NO_SUCH_FILE;
}
return X_STATUS_UNSUCCESSFUL;
}
} else {
if (FindNextFile(handle, &ffd) == FALSE) {
FindClose(handle);
find_file_ = INVALID_HANDLE_VALUE;
return X_STATUS_NO_SUCH_FILE;
}
}
auto end = (uint8_t*)out_info + length;
size_t entry_name_length = wcslen(ffd.cFileName);
if (((uint8_t*)&out_info->file_name[0]) + entry_name_length > end) {
FindClose(handle);
find_file_ = INVALID_HANDLE_VALUE;
return X_STATUS_BUFFER_OVERFLOW;
}
uint64_t file_size = ((uint64_t)ffd.nFileSizeHigh << 32) | ffd.nFileSizeLow;
out_info->next_entry_offset = 0;
out_info->file_index = 0xCDCDCDCD;
out_info->creation_time = COMBINE_TIME(ffd.ftCreationTime);
out_info->last_access_time = COMBINE_TIME(ffd.ftLastAccessTime);
out_info->last_write_time = COMBINE_TIME(ffd.ftLastWriteTime);
out_info->change_time = COMBINE_TIME(ffd.ftLastWriteTime);
out_info->end_of_file = file_size;
out_info->allocation_size = xe::round_up(file_size, 4096);
out_info->attributes = (X_FILE_ATTRIBUTES)ffd.dwFileAttributes;
out_info->file_name_length = (uint32_t)entry_name_length;
for (size_t i = 0; i < entry_name_length; ++i) {
out_info->file_name[i] =
ffd.cFileName[i] < 256 ? (char)ffd.cFileName[i] : '?';
}
return X_STATUS_SUCCESS;
}
HostPathEntry::~HostPathEntry() = default;
X_STATUS HostPathEntry::Open(KernelState* kernel_state, Mode mode, bool async,
XFile** out_file) {

View File

@ -10,23 +10,23 @@
#ifndef XENIA_VFS_DEVICES_HOST_PATH_ENTRY_H_
#define XENIA_VFS_DEVICES_HOST_PATH_ENTRY_H_
#include <string>
#include "xenia/vfs/entry.h"
namespace xe {
namespace vfs {
class HostPathDevice;
class HostPathEntry : public Entry {
public:
HostPathEntry(Device* device, const char* path,
HostPathEntry(Device* device, std::string path,
const std::wstring& local_path);
~HostPathEntry() override;
const std::wstring& local_path() { return local_path_; }
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
X_STATUS Open(KernelState* kernel_state, Mode mode, bool async,
XFile** out_file) override;
@ -36,8 +36,9 @@ class HostPathEntry : public Entry {
size_t length) override;
private:
friend class HostPathDevice;
std::wstring local_path_;
HANDLE find_file_;
};
} // namespace vfs

View File

@ -9,7 +9,6 @@
#include "xenia/vfs/devices/host_path_file.h"
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/host_path_entry.h"
namespace xe {
@ -17,28 +16,11 @@ namespace vfs {
HostPathFile::HostPathFile(KernelState* kernel_state, Mode mode,
HostPathEntry* entry, HANDLE file_handle)
: entry_(entry), file_handle_(file_handle), XFile(kernel_state, mode) {}
: XFile(kernel_state, mode, entry),
entry_(entry),
file_handle_(file_handle) {}
HostPathFile::~HostPathFile() {
CloseHandle(file_handle_);
delete entry_;
}
const std::string& HostPathFile::path() const { return entry_->path(); }
const std::string& HostPathFile::name() const { return entry_->name(); }
Device* HostPathFile::device() const { return entry_->device(); }
X_STATUS HostPathFile::QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
return entry_->QueryInfo(out_info);
}
X_STATUS HostPathFile::QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) {
return entry_->QueryDirectory(out_info, length, file_name, restart);
}
HostPathFile::~HostPathFile() { CloseHandle(file_handle_); }
X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) {

View File

@ -25,15 +25,6 @@ class HostPathFile : public XFile {
HANDLE file_handle);
~HostPathFile() override;
const std::string& path() const override;
const std::string& name() const override;
Device* device() const override;
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
protected:
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
size_t* out_bytes_read) override;

View File

@ -11,18 +11,25 @@
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/vfs/stfs.h"
#include "xenia/vfs/devices/stfs_container_entry.h"
#include "xenia/kernel/objects/xfile.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), stfs_(nullptr) {}
: Device(mount_path), local_path_(local_path) {}
STFSContainerDevice::~STFSContainerDevice() { delete stfs_; }
STFSContainerDevice::~STFSContainerDevice() = default;
bool STFSContainerDevice::Initialize() {
mmap_ = MappedMemory::Open(local_path_, MappedMemory::Mode::kRead);
@ -31,39 +38,277 @@ bool STFSContainerDevice::Initialize() {
return false;
}
stfs_ = new STFS(mmap_.get());
STFS::Error error = stfs_->Load();
if (error != STFS::kSuccess) {
XELOGE("STFS init failed: %d", error);
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;
}
// stfs_->Dump();
result = ReadAllEntries(map_ptr);
if (result != Error::kSuccess) {
XELOGE("STFS entry reading failed: %d", result);
return false;
}
return true;
}
std::unique_ptr<Entry> STFSContainerDevice::ResolvePath(const char* path) {
// The filesystem will have stripped our prefix off already, so the path will
// be in the form:
// some\PATH.foo
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;
}
XELOGFS("STFSContainerDevice::ResolvePath(%s)", path);
// Read header.
if (!header_.Read(map_ptr)) {
return Error::kErrorDamagedFile;
}
STFSEntry* stfs_entry = stfs_->root_entry();
if (((header_.header_size + 0x0FFF) & 0xB000) == 0xB000) {
table_size_shift_ = 0;
} else {
table_size_shift_ = 1;
}
// Walk the path, one separator at a time.
auto path_parts = xe::split_path(path);
for (auto& part : path_parts) {
stfs_entry = stfs_entry->GetChild(part.c_str());
if (!stfs_entry) {
// Not found.
return nullptr;
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 std::make_unique<STFSContainerEntry>(this, path, mmap_.get(),
stfs_entry);
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

View File

@ -19,7 +19,107 @@
namespace xe {
namespace vfs {
class STFS;
// http://www.free60.org/STFS
enum class StfsPackageType {
STFS_PACKAGE_CON,
STFS_PACKAGE_PIRS,
STFS_PACKAGE_LIVE,
};
enum STFSContentType : uint32_t {
STFS_CONTENT_ARCADE_TITLE = 0x000D0000,
STFS_CONTENT_AVATAR_ITEM = 0x00009000,
STFS_CONTENT_CACHE_FILE = 0x00040000,
STFS_CONTENT_COMMUNITY_GAME = 0x02000000,
STFS_CONTENT_GAME_DEMO = 0x00080000,
STFS_CONTENT_GAMER_PICTURE = 0x00020000,
STFS_CONTENT_GAME_TITLE = 0x000A0000,
STFS_CONTENT_GAME_TRAILER = 0x000C0000,
STFS_CONTENT_GAME_VIDEO = 0x00400000,
STFS_CONTENT_INSTALLED_GAME = 0x00004000,
STFS_CONTENT_INSTALLER = 0x000B0000,
STFS_CONTENT_IPTV_PAUSE_BUFFER = 0x00002000,
STFS_CONTENT_LICENSE_STORE = 0x000F0000,
STFS_CONTENT_MARKETPLACE_CONTENT = 0x00000002,
STFS_CONTENT_MOVIE = 0x00100000,
STFS_CONTENT_MUSIC_VIDEO = 0x00300000,
STFS_CONTENT_PODCAST_VIDEO = 0x00500000,
STFS_CONTENT_PROFILE = 0x00010000,
STFS_CONTENT_PUBLISHER = 0x00000003,
STFS_CONTENT_SAVED_GAME = 0x00000001,
STFS_CONTENT_STORAGE_DOWNLOAD = 0x00050000,
STFS_CONTENT_THEME = 0x00030000,
STFS_CONTENT_TV = 0x00200000,
STFS_CONTENT_VIDEO = 0x00090000,
STFS_CONTENT_VIRAL_VIDEO = 0x00600000,
STFS_CONTENT_XBOX_DOWNLOAD = 0x00070000,
STFS_CONTENT_XBOX_ORIGINAL_GAME = 0x00005000,
STFS_CONTENT_XBOX_SAVED_GAME = 0x00060000,
STFS_CONTENT_XBOX_360_TITLE = 0x00001000,
STFS_CONTENT_XBOX_TITLE = 0x00005000,
STFS_CONTENT_XNA = 0x000E0000,
};
enum class StfsPlatform : uint8_t {
STFS_PLATFORM_XBOX_360 = 0x02,
STFS_PLATFORM_PC = 0x04,
};
enum class StfsDescriptorType : uint32_t {
STFS_DESCRIPTOR_STFS = 0,
STFS_DESCRIPTOR_SVOD = 1,
};
struct StfsVolumeDescriptor {
bool Read(const uint8_t* p);
uint8_t descriptor_size;
uint8_t reserved;
uint8_t block_separation;
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;
};
class StfsHeader {
public:
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];
StfsVolumeDescriptor volume_descriptor;
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];
};
class STFSContainerDevice : public Device {
public:
@ -27,9 +127,7 @@ class STFSContainerDevice : public Device {
const std::wstring& local_path);
~STFSContainerDevice() override;
bool Initialize();
std::unique_ptr<Entry> ResolvePath(const char* path) override;
bool Initialize() override;
uint32_t total_allocation_units() const override {
return uint32_t(mmap_->size() / sectors_per_allocation_unit() /
@ -40,9 +138,33 @@ class STFSContainerDevice : public Device {
uint32_t bytes_per_sector() const override { return 4 * 1024; }
private:
enum class Error {
kSuccess = 0,
kErrorOutOfMemory = -1,
kErrorReadError = -10,
kErrorFileMismatch = -30,
kErrorDamagedFile = -31,
};
struct BlockHash {
uint32_t next_block_index;
uint32_t info;
};
Error ReadHeaderAndVerify(const uint8_t* map_ptr);
Error ReadAllEntries(const uint8_t* map_ptr);
size_t BlockToOffset(uint32_t block);
uint32_t ComputeBlockNumber(uint32_t block_index);
BlockHash GetBlockHash(const uint8_t* map_ptr, uint32_t block_index,
uint32_t table_offset);
std::wstring local_path_;
std::unique_ptr<MappedMemory> mmap_;
STFS* stfs_;
StfsPackageType package_type_;
StfsHeader header_;
uint32_t table_size_shift_;
};
} // namespace vfs

View File

@ -15,79 +15,12 @@
namespace xe {
namespace vfs {
STFSContainerEntry::STFSContainerEntry(Device* device, const char* path,
MappedMemory* mmap,
STFSEntry* stfs_entry)
: Entry(device, path),
mmap_(mmap),
stfs_entry_(stfs_entry),
it_(stfs_entry_->children.begin()) {}
STFSContainerEntry::STFSContainerEntry(Device* device, std::string path,
MappedMemory* mmap)
: Entry(device, path), mmap_(mmap), data_offset_(0), data_size_(0) {}
STFSContainerEntry::~STFSContainerEntry() = default;
X_STATUS STFSContainerEntry::QueryInfo(
X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
assert_not_null(out_info);
out_info->creation_time = stfs_entry_->update_timestamp;
out_info->last_access_time = stfs_entry_->access_timestamp;
out_info->last_write_time = stfs_entry_->update_timestamp;
out_info->change_time = stfs_entry_->update_timestamp;
out_info->allocation_size = xe::round_up(stfs_entry_->size, 4096);
out_info->end_of_file = stfs_entry_->size;
out_info->attributes = stfs_entry_->attributes;
return X_STATUS_SUCCESS;
}
X_STATUS STFSContainerEntry::QueryDirectory(
X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) {
assert_not_null(out_info);
STFSEntry* entry(nullptr);
if (file_name != nullptr) {
// Only queries in the current directory are supported for now
assert_true(std::strchr(file_name, '\\') == nullptr);
find_engine_.SetRule(file_name);
// Always restart the search?
it_ = stfs_entry_->children.begin();
entry = stfs_entry_->GetChild(find_engine_, it_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
} else {
if (restart) {
it_ = stfs_entry_->children.begin();
}
entry = stfs_entry_->GetChild(find_engine_, it_);
if (!entry) {
return X_STATUS_NO_SUCH_FILE;
}
auto end = (uint8_t*)out_info + length;
auto entry_name = entry->name;
if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) {
return X_STATUS_NO_SUCH_FILE;
}
}
out_info->file_index = 0xCDCDCDCD;
out_info->creation_time = entry->update_timestamp;
out_info->last_access_time = entry->access_timestamp;
out_info->last_write_time = entry->update_timestamp;
out_info->change_time = entry->update_timestamp;
out_info->end_of_file = entry->size;
out_info->allocation_size = xe::round_up(entry->size, 4096);
out_info->attributes = entry->attributes;
out_info->file_name_length = static_cast<uint32_t>(entry->name.size());
memcpy(out_info->file_name, entry->name.c_str(), entry->name.size());
return X_STATUS_SUCCESS;
}
X_STATUS STFSContainerEntry::Open(KernelState* kernel_state, Mode mode,
bool async, XFile** out_file) {
*out_file = new STFSContainerFile(kernel_state, mode, this);

View File

@ -11,38 +11,41 @@
#define XENIA_VFS_DEVICES_STFS_CONTAINER_ENTRY_H_
#include <vector>
#include <iterator>
#include "xenia/base/filesystem.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/entry.h"
#include "xenia/vfs/stfs.h"
namespace xe {
namespace vfs {
class STFSContainerDevice;
class STFSContainerEntry : public Entry {
public:
STFSContainerEntry(Device* device, const char* path, MappedMemory* mmap,
STFSEntry* stfs_entry);
STFSContainerEntry(Device* device, std::string path, MappedMemory* mmap);
~STFSContainerEntry() override;
MappedMemory* mmap() const { return mmap_; }
STFSEntry* stfs_entry() const { return stfs_entry_; }
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; }
X_STATUS Open(KernelState* kernel_state, Mode desired_access, bool async,
XFile** out_file) override;
private:
MappedMemory* mmap_;
STFSEntry* stfs_entry_;
struct BlockRecord {
size_t offset;
size_t length;
};
const std::vector<BlockRecord>& block_list() const { return block_list_; }
xe::filesystem::WildcardEngine find_engine_;
STFSEntry::child_it_t it_;
private:
friend class STFSContainerDevice;
MappedMemory* mmap_;
size_t data_offset_;
size_t data_size_;
std::vector<BlockRecord> block_list_;
};
} // namespace vfs

View File

@ -11,56 +11,36 @@
#include <algorithm>
#include "xenia/vfs/device.h"
#include "xenia/vfs/devices/stfs_container_entry.h"
#include "xenia/vfs/stfs.h"
namespace xe {
namespace vfs {
STFSContainerFile::STFSContainerFile(KernelState* kernel_state, Mode mode,
STFSContainerEntry* entry)
: XFile(kernel_state, mode), entry_(entry) {}
: XFile(kernel_state, mode, entry), entry_(entry) {}
STFSContainerFile::~STFSContainerFile() { delete entry_; }
const std::string& STFSContainerFile::path() const { return entry_->path(); }
const std::string& STFSContainerFile::name() const { return entry_->name(); }
Device* STFSContainerFile::device() const { return entry_->device(); }
X_STATUS STFSContainerFile::QueryInfo(
X_FILE_NETWORK_OPEN_INFORMATION* out_info) {
return entry_->QueryInfo(out_info);
}
X_STATUS STFSContainerFile::QueryDirectory(
X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) {
return entry_->QueryDirectory(out_info, length, file_name, restart);
}
STFSContainerFile::~STFSContainerFile() = default;
X_STATUS STFSContainerFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset,
size_t* out_bytes_read) {
STFSEntry* stfs_entry = entry_->stfs_entry();
if (byte_offset >= stfs_entry->size) {
if (byte_offset >= entry_->size()) {
return X_STATUS_END_OF_FILE;
}
// Each block is 4096.
// Blocks may not be sequential, so we need to read by blocks and handle the
// offsets.
size_t real_length = std::min(buffer_length, stfs_entry->size - byte_offset);
size_t real_length = std::min(buffer_length, entry_->size() - byte_offset);
size_t start_block = byte_offset / 4096;
size_t end_block =
std::min(stfs_entry->block_list.size(),
std::min(entry_->block_list().size(),
(size_t)ceil((byte_offset + real_length) / 4096.0));
uint8_t* dest_ptr = (uint8_t*)buffer;
size_t remaining_length = real_length;
for (size_t n = start_block; n < end_block; n++) {
auto& record = stfs_entry->block_list[n];
auto& record = entry_->block_list()[n];
size_t offset = record.offset;
size_t read_length = std::min(remaining_length, record.length);
if (n == start_block) {

View File

@ -23,15 +23,6 @@ class STFSContainerFile : public XFile {
STFSContainerEntry* entry);
~STFSContainerFile() override;
const std::string& path() const override;
const std::string& name() const override;
Device* device() const override;
X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) override;
X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info, size_t length,
const char* file_name, bool restart) override;
protected:
X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset,
size_t* out_bytes_read) override;

View File

@ -16,7 +16,14 @@ namespace xe {
namespace vfs {
Entry::Entry(Device* device, const std::string& path)
: device_(device), path_(path) {
: device_(device),
path_(path),
attributes_(0),
size_(0),
allocation_size_(0),
create_timestamp_(0),
access_timestamp_(0),
write_timestamp_(0) {
assert_not_null(device);
absolute_path_ = xe::join_paths(device->mount_path(), path);
name_ = xe::find_name_from_path(path);
@ -24,7 +31,40 @@ Entry::Entry(Device* device, const std::string& path)
Entry::~Entry() = default;
void Entry::Dump(xe::StringBuffer* string_buffer, int indent) {
for (int i = 0; i < indent; ++i) {
string_buffer->Append(' ');
}
string_buffer->Append(name());
string_buffer->Append('\n');
for (auto& child : children_) {
child->Dump(string_buffer, indent + 2);
}
}
bool Entry::is_read_only() const { return device_->is_read_only(); }
Entry* Entry::GetChild(std::string name) {
// TODO(benvanik): a faster search
for (auto& child : children_) {
if (strcasecmp(child->name().c_str(), name.c_str()) == 0) {
return child.get();
}
}
return nullptr;
}
Entry* Entry::IterateChildren(const xe::filesystem::WildcardEngine& engine,
size_t* current_index) {
while (*current_index < children_.size()) {
auto& child = children_[*current_index];
*current_index = *current_index + 1;
if (engine.Match(child->name())) {
return child.get();
}
}
return nullptr;
}
} // namespace vfs
} // namespace xe

View File

@ -13,15 +13,15 @@
#include <memory>
#include <string>
#include "xenia/base/filesystem.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/base/string_buffer.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
class KernelState;
class XFile;
struct X_FILE_NETWORK_OPEN_INFORMATION;
class X_FILE_DIRECTORY_INFORMATION;
} // namespace kernel
} // namespace xe
@ -38,22 +38,44 @@ enum class Mode {
READ_APPEND,
};
enum FileAttributeFlags : uint32_t {
kFileAttributeNone = 0x0000,
kFileAttributeReadOnly = 0x0001,
kFileAttributeHidden = 0x0002,
kFileAttributeSystem = 0x0004,
kFileAttributeDirectory = 0x0010,
kFileAttributeArchive = 0x0020,
kFileAttributeDevice = 0x0040,
kFileAttributeNormal = 0x0080,
kFileAttributeTemporary = 0x0100,
kFileAttributeCompressed = 0x0800,
kFileAttributeEncrypted = 0x4000,
};
class Entry {
public:
Entry(Device* device, const std::string& path);
virtual ~Entry();
void Dump(xe::StringBuffer* string_buffer, int indent);
Device* device() const { return device_; }
const std::string& path() const { return path_; }
const std::string& absolute_path() const { return absolute_path_; }
const std::string& name() const { return name_; }
uint32_t attributes() const { return attributes_; }
size_t size() const { return size_; }
size_t allocation_size() const { return allocation_size_; }
uint64_t create_timestamp() const { return create_timestamp_; }
uint64_t access_timestamp() const { return access_timestamp_; }
uint64_t write_timestamp() const { return write_timestamp_; }
bool is_read_only() const;
virtual X_STATUS QueryInfo(X_FILE_NETWORK_OPEN_INFORMATION* out_info) = 0;
virtual X_STATUS QueryDirectory(X_FILE_DIRECTORY_INFORMATION* out_info,
size_t length, const char* file_name,
bool restart) = 0;
Entry* GetChild(std::string name);
size_t child_count() const { return children_.size(); }
Entry* IterateChildren(const xe::filesystem::WildcardEngine& engine,
size_t* current_index);
virtual X_STATUS Open(KernelState* kernel_state, Mode mode, bool async,
XFile** out_file) = 0;
@ -65,11 +87,20 @@ class Entry {
return nullptr;
}
private:
protected:
Entry(Device* device, const std::string& path);
Device* device_;
std::string path_;
std::string absolute_path_;
std::string name_;
uint32_t attributes_; // FileAttributeFlags
size_t size_;
size_t allocation_size_;
uint64_t create_timestamp_;
uint64_t access_timestamp_;
uint64_t write_timestamp_;
std::vector<std::unique_ptr<Entry>> children_;
};
} // namespace vfs

View File

@ -1,205 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
* Major contributions to this file from:
* - abgx360
*/
#include "xenia/vfs/gdfx.h"
#include "xenia/base/math.h"
namespace xe {
namespace vfs {
const size_t kXESectorSize = 2048;
GDFXEntry::GDFXEntry()
: attributes(X_FILE_ATTRIBUTE_NONE), offset(0), size(0) {}
GDFXEntry::~GDFXEntry() {
for (std::vector<GDFXEntry*>::iterator it = children.begin();
it != children.end(); ++it) {
delete *it;
}
}
GDFXEntry* GDFXEntry::GetChild(const xe::filesystem::WildcardEngine& engine,
child_it_t& ref_it) {
GDFXEntry* child_entry(nullptr);
while (ref_it != children.end()) {
if (engine.Match((*ref_it)->name)) {
child_entry = (*ref_it);
++ref_it;
break;
}
++ref_it;
}
return child_entry;
}
GDFXEntry* GDFXEntry::GetChild(const char* name) {
// TODO(benvanik): a faster search
for (std::vector<GDFXEntry*>::iterator it = children.begin();
it != children.end(); ++it) {
GDFXEntry* entry = *it;
if (strcasecmp(entry->name.c_str(), name) == 0) {
return entry;
}
}
return NULL;
}
void GDFXEntry::Dump(int indent) {
printf("%s%s\n", std::string(indent, ' ').c_str(), name.c_str());
for (std::vector<GDFXEntry*>::iterator it = children.begin();
it != children.end(); ++it) {
GDFXEntry* entry = *it;
entry->Dump(indent + 2);
}
}
GDFX::GDFX(MappedMemory* mmap) : mmap_(mmap) { root_entry_ = nullptr; }
GDFX::~GDFX() { delete root_entry_; }
GDFXEntry* GDFX::root_entry() { return root_entry_; }
GDFX::Error GDFX::Load() {
ParseState state = {0};
state.ptr = mmap_->data();
state.size = mmap_->size();
auto result = Verify(state);
if (result != kSuccess) {
return result;
}
result = ReadAllEntries(state, state.ptr + state.root_offset);
if (result != kSuccess) {
return result;
}
return kSuccess;
}
void GDFX::Dump() {
if (root_entry_) {
root_entry_->Dump(0);
}
}
GDFX::Error GDFX::Verify(ParseState& state) {
// Find sector 32 of the game partition - try at a few points.
const static size_t likely_offsets[] = {
0x00000000, 0x0000FB20, 0x00020600, 0x0FD90000,
};
bool magic_found = false;
for (size_t n = 0; n < xe::countof(likely_offsets); n++) {
state.game_offset = likely_offsets[n];
if (VerifyMagic(state, state.game_offset + (32 * kXESectorSize))) {
magic_found = true;
break;
}
}
if (!magic_found) {
// File doesn't have the magic values - likely not a real GDFX source.
return kErrorFileMismatch;
}
// Read sector 32 to get FS state.
if (state.size < state.game_offset + (32 * kXESectorSize)) {
return kErrorReadError;
}
uint8_t* fs_ptr = state.ptr + state.game_offset + (32 * kXESectorSize);
state.root_sector = xe::load<uint32_t>(fs_ptr + 20);
state.root_size = xe::load<uint32_t>(fs_ptr + 24);
state.root_offset = state.game_offset + (state.root_sector * kXESectorSize);
if (state.root_size < 13 || state.root_size > 32 * 1024 * 1024) {
return kErrorDamagedFile;
}
return kSuccess;
}
bool GDFX::VerifyMagic(ParseState& state, size_t offset) {
// Simple check to see if the given offset contains the magic value.
return memcmp(state.ptr + offset, "MICROSOFT*XBOX*MEDIA", 20) == 0;
}
GDFX::Error GDFX::ReadAllEntries(ParseState& state,
const uint8_t* root_buffer) {
root_entry_ = new GDFXEntry();
root_entry_->offset = 0;
root_entry_->size = 0;
root_entry_->name = "";
root_entry_->attributes = X_FILE_ATTRIBUTE_DIRECTORY;
if (!ReadEntry(state, root_buffer, 0, root_entry_)) {
return kErrorOutOfMemory;
}
return kSuccess;
}
bool GDFX::ReadEntry(ParseState& state, const uint8_t* buffer,
uint16_t entry_ordinal, GDFXEntry* parent) {
const uint8_t* p = buffer + (entry_ordinal * 4);
uint16_t node_l = xe::load<uint16_t>(p + 0);
uint16_t node_r = xe::load<uint16_t>(p + 2);
size_t sector = xe::load<uint32_t>(p + 4);
size_t length = xe::load<uint32_t>(p + 8);
uint8_t attributes = xe::load<uint8_t>(p + 12);
uint8_t name_length = xe::load<uint8_t>(p + 13);
char* name = (char*)(p + 14);
if (node_l && !ReadEntry(state, buffer, node_l, parent)) {
return false;
}
GDFXEntry* entry = new GDFXEntry();
entry->name = std::string(name, name_length);
entry->attributes = (X_FILE_ATTRIBUTES)attributes;
// Add to parent.
parent->children.push_back(entry);
if (attributes & X_FILE_ATTRIBUTE_DIRECTORY) {
// Folder.
entry->offset = 0;
entry->size = 0;
if (length) {
// Not a leaf - read in children.
if (state.size < state.game_offset + (sector * kXESectorSize)) {
// Out of bounds read.
return false;
}
// Read child list.
uint8_t* folder_ptr =
state.ptr + state.game_offset + (sector * kXESectorSize);
if (!ReadEntry(state, folder_ptr, 0, entry)) {
return false;
}
}
} else {
// File.
entry->offset = state.game_offset + (sector * kXESectorSize);
entry->size = length;
}
// Read next file in the list.
if (node_r && !ReadEntry(state, buffer, node_r, parent)) {
return false;
}
return true;
}
} // namespace vfs
} // namespace xe

View File

@ -1,96 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_VFS_GDFX_H_
#define XENIA_VFS_GDFX_H_
#include <vector>
#include "xenia/base/filesystem.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/entry.h"
#include "xenia/xbox.h"
namespace xe {
namespace vfs {
class GDFX;
class GDFXEntry {
public:
GDFXEntry();
~GDFXEntry();
typedef std::vector<GDFXEntry*> child_t;
typedef child_t::iterator child_it_t;
GDFXEntry* GetChild(const xe::filesystem::WildcardEngine& engine,
child_it_t& ref_it);
GDFXEntry* GetChild(const char* name);
void Dump(int indent);
std::string name;
X_FILE_ATTRIBUTES attributes;
size_t offset;
size_t size;
child_t children;
};
class GDFX {
public:
enum Error {
kSuccess = 0,
kErrorOutOfMemory = -1,
kErrorReadError = -10,
kErrorFileMismatch = -30,
kErrorDamagedFile = -31,
};
GDFX(MappedMemory* mmap);
virtual ~GDFX();
GDFXEntry* root_entry();
Error Load();
void Dump();
private:
typedef struct {
uint8_t* ptr;
// Size (bytes) of total image.
size_t size;
// Offset (bytes) of game partition.
size_t game_offset;
// Offset (sector) of root.
size_t root_sector;
// Offset (bytes) of root.
size_t root_offset;
// Size (bytes) of root.
size_t root_size;
} ParseState;
Error Verify(ParseState& state);
bool VerifyMagic(ParseState& state, size_t offset);
Error ReadAllEntries(ParseState& state, const uint8_t* root_buffer);
bool ReadEntry(ParseState& state, const uint8_t* buffer,
uint16_t entry_ordinal, GDFXEntry* parent);
MappedMemory* mmap_;
GDFXEntry* root_entry_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_GDFX_H_

View File

@ -1,343 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
* Major contributions to this file from:
* - free60
*/
#include "xenia/vfs/stfs.h"
#include <algorithm>
#include "xenia/base/logging.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))
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);
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) {
memcpy(license_entries, p + 0x22C, 0x100);
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);
memcpy(console_id, p + 0x36C, 0x5);
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 != 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);
memcpy(thumbnail_image, p + 0x171A, 0x4000);
memcpy(title_thumbnail_image, p + 0x571A, 0x4000);
return true;
}
STFSEntry::STFSEntry()
: attributes(X_FILE_ATTRIBUTE_NONE),
offset(0),
size(0),
update_timestamp(0),
access_timestamp(0) {}
STFSEntry* STFSEntry::GetChild(const xe::filesystem::WildcardEngine& engine,
child_it_t& ref_it) {
STFSEntry* child_entry(nullptr);
while (ref_it != children.end()) {
if (engine.Match((*ref_it)->name)) {
child_entry = (*ref_it).get();
++ref_it;
break;
}
++ref_it;
}
return child_entry;
}
STFSEntry* STFSEntry::GetChild(const char* name) {
// TODO(benvanik): a faster search
for (const auto& entry : children) {
if (strcasecmp(entry->name.c_str(), name) == 0) {
return entry.get();
}
}
return nullptr;
}
void STFSEntry::Dump(int indent) {
printf("%s%s\n", std::string(indent, ' ').c_str(), name.c_str());
for (const auto& entry : children) {
entry->Dump(indent + 2);
}
}
STFS::STFS(MappedMemory* mmap) : mmap_(mmap) {}
STFS::~STFS() {}
STFS::Error STFS::Load() {
uint8_t* map_ptr = mmap_->data();
auto result = ReadHeaderAndVerify(map_ptr);
if (result != kSuccess) {
return result;
}
result = ReadAllEntries(map_ptr);
if (result != kSuccess) {
return result;
}
return kSuccess;
}
void STFS::Dump() {
if (root_entry_) {
root_entry_->Dump(0);
}
}
STFS::Error STFS::ReadHeaderAndVerify(const uint8_t* map_ptr) {
// Check signature.
if (memcmp(map_ptr, "LIVE", 4) == 0) {
package_type_ = STFS_PACKAGE_LIVE;
} else if (memcmp(map_ptr, "PIRS", 4) == 0) {
package_type_ = STFS_PACKAGE_PIRS;
} else if (memcmp(map_ptr, "CON", 3) == 0) {
package_type_ = STFS_PACKAGE_CON;
} else {
// Unexpected format.
return STFS::Error::kErrorFileMismatch;
}
// Read header.
if (!header_.Read(map_ptr)) {
return STFS::Error::kErrorDamagedFile;
}
if (((header_.header_size + 0x0FFF) & 0xB000) == 0xB000) {
table_size_shift_ = 0;
} else {
table_size_shift_ = 1;
}
return kSuccess;
}
STFS::Error STFS::ReadAllEntries(const uint8_t* map_ptr) {
root_entry_.reset(new STFSEntry());
root_entry_->attributes = X_FILE_ATTRIBUTE_DIRECTORY;
std::vector<STFSEntry*> 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 = std::make_unique<STFSEntry>();
entry->name = std::string((char*)filename, filename_length_flags & 0x3F);
// bit 0x40 = consecutive blocks (not fragmented?)
if (filename_length_flags & 0x80) {
entry->attributes = X_FILE_ATTRIBUTE_DIRECTORY;
} else {
entry->attributes = X_FILE_ATTRIBUTE_NORMAL;
entry->offset = BlockToOffset(ComputeBlockNumber(start_block_index));
entry->size = file_size;
}
entry->update_timestamp = update_timestamp;
entry->access_timestamp = access_timestamp;
entries.push_back(entry.get());
// 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.push_back(std::move(entry));
} else {
// Lookup and add.
auto parent = entries[path_indicator];
parent->children.push_back(std::move(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 kSuccess;
}
size_t STFS::BlockToOffset(uint32_t block) {
if (block >= 0xFFFFFF) {
return -1;
} else {
return ((header_.header_size + 0x0FFF) & 0xF000) + (block << 12);
}
}
uint32_t STFS::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_ == STFS_PACKAGE_CON) {
base <<= block_shift;
}
uint32_t block = base + block_index;
if (block_index >= 0xAA) {
base = (block_index + 0x70E4) / 0x70E4;
if (package_type_ == STFS_PACKAGE_CON) {
base <<= block_shift;
}
block += base;
if (block_index >= 0x70E4) {
base = (block_index + 0x4AF768) / 0x4AF768;
if (package_type_ == STFS_PACKAGE_CON) {
base <<= block_shift;
}
block += base;
}
}
return block;
}
STFS::BlockHash_t STFS::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};
}
} // namespace vfs
} // namespace xe

View File

@ -1,200 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_VFS_STFS_H_
#define XENIA_VFS_STFS_H_
#include <memory>
#include <vector>
#include "xenia/base/filesystem.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/vfs/entry.h"
#include "xenia/xbox.h"
namespace xe {
namespace vfs {
class STFS;
// http://www.free60.org/STFS
enum STFSPackageType {
STFS_PACKAGE_CON,
STFS_PACKAGE_PIRS,
STFS_PACKAGE_LIVE,
};
enum STFSContentType : uint32_t {
STFS_CONTENT_ARCADE_TITLE = 0x000D0000,
STFS_CONTENT_AVATAR_ITEM = 0x00009000,
STFS_CONTENT_CACHE_FILE = 0x00040000,
STFS_CONTENT_COMMUNITY_GAME = 0x02000000,
STFS_CONTENT_GAME_DEMO = 0x00080000,
STFS_CONTENT_GAMER_PICTURE = 0x00020000,
STFS_CONTENT_GAME_TITLE = 0x000A0000,
STFS_CONTENT_GAME_TRAILER = 0x000C0000,
STFS_CONTENT_GAME_VIDEO = 0x00400000,
STFS_CONTENT_INSTALLED_GAME = 0x00004000,
STFS_CONTENT_INSTALLER = 0x000B0000,
STFS_CONTENT_IPTV_PAUSE_BUFFER = 0x00002000,
STFS_CONTENT_LICENSE_STORE = 0x000F0000,
STFS_CONTENT_MARKETPLACE_CONTENT = 0x00000002,
STFS_CONTENT_MOVIE = 0x00100000,
STFS_CONTENT_MUSIC_VIDEO = 0x00300000,
STFS_CONTENT_PODCAST_VIDEO = 0x00500000,
STFS_CONTENT_PROFILE = 0x00010000,
STFS_CONTENT_PUBLISHER = 0x00000003,
STFS_CONTENT_SAVED_GAME = 0x00000001,
STFS_CONTENT_STORAGE_DOWNLOAD = 0x00050000,
STFS_CONTENT_THEME = 0x00030000,
STFS_CONTENT_TV = 0x00200000,
STFS_CONTENT_VIDEO = 0x00090000,
STFS_CONTENT_VIRAL_VIDEO = 0x00600000,
STFS_CONTENT_XBOX_DOWNLOAD = 0x00070000,
STFS_CONTENT_XBOX_ORIGINAL_GAME = 0x00005000,
STFS_CONTENT_XBOX_SAVED_GAME = 0x00060000,
STFS_CONTENT_XBOX_360_TITLE = 0x00001000,
STFS_CONTENT_XBOX_TITLE = 0x00005000,
STFS_CONTENT_XNA = 0x000E0000,
};
enum STFSPlatform : uint8_t {
STFS_PLATFORM_XBOX_360 = 0x02,
STFS_PLATFORM_PC = 0x04,
};
enum STFSDescriptorType : uint32_t {
STFS_DESCRIPTOR_STFS = 0,
STFS_DESCRIPTOR_SVOD = 1,
};
class STFSVolumeDescriptor {
public:
bool Read(const uint8_t* p);
uint8_t descriptor_size;
uint8_t reserved;
uint8_t block_separation;
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;
};
class STFSHeader {
public:
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];
STFSVolumeDescriptor volume_descriptor;
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];
};
class STFSEntry {
public:
STFSEntry();
typedef std::vector<std::unique_ptr<STFSEntry>> child_t;
typedef child_t::iterator child_it_t;
STFSEntry* GetChild(const xe::filesystem::WildcardEngine& engine,
child_it_t& ref_it);
STFSEntry* GetChild(const char* name);
void Dump(int indent);
std::string name;
X_FILE_ATTRIBUTES attributes;
size_t offset;
size_t size;
uint32_t update_timestamp;
uint32_t access_timestamp;
child_t children;
typedef struct {
size_t offset;
size_t length;
} BlockRecord_t;
std::vector<BlockRecord_t> block_list;
};
class STFS {
public:
enum Error {
kSuccess = 0,
kErrorOutOfMemory = -1,
kErrorReadError = -10,
kErrorFileMismatch = -30,
kErrorDamagedFile = -31,
};
STFS(MappedMemory* mmap);
virtual ~STFS();
const STFSHeader* header() const { return &header_; }
STFSEntry* root_entry() const { return root_entry_.get(); }
Error Load();
void Dump();
private:
Error ReadHeaderAndVerify(const uint8_t* map_ptr);
Error ReadAllEntries(const uint8_t* map_ptr);
size_t BlockToOffset(uint32_t block);
uint32_t ComputeBlockNumber(uint32_t block_index);
typedef struct {
uint32_t next_block_index;
uint32_t info;
} BlockHash_t;
BlockHash_t GetBlockHash(const uint8_t* map_ptr, uint32_t block_index,
uint32_t table_offset);
MappedMemory* mmap_;
STFSPackageType package_type_;
STFSHeader header_;
uint32_t table_size_shift_;
std::unique_ptr<STFSEntry> root_entry_;
};
} // namespace vfs
} // namespace xe
#endif // XENIA_VFS_STFS_H_

View File

@ -45,7 +45,7 @@ bool VirtualFileSystem::UnregisterSymbolicLink(std::string path) {
return true;
}
std::unique_ptr<Entry> VirtualFileSystem::ResolvePath(const std::string& path) {
Entry* VirtualFileSystem::ResolvePath(std::string path) {
// Resolve relative paths
std::string normalized_path(xe::filesystem::CanonicalizePath(path));
@ -88,15 +88,5 @@ std::unique_ptr<Entry> VirtualFileSystem::ResolvePath(const std::string& path) {
return nullptr;
}
X_STATUS VirtualFileSystem::Open(std::unique_ptr<Entry> entry,
KernelState* kernel_state, Mode mode,
bool async, XFile** out_file) {
auto result = entry->Open(kernel_state, mode, async, out_file);
if (XSUCCEEDED(result)) {
entry.release();
}
return result;
}
} // namespace vfs
} // namespace xe

View File

@ -31,9 +31,7 @@ class VirtualFileSystem {
bool RegisterSymbolicLink(std::string path, std::string target);
bool UnregisterSymbolicLink(std::string path);
std::unique_ptr<Entry> ResolvePath(const std::string& path);
X_STATUS Open(std::unique_ptr<Entry> entry, KernelState* kernel_state,
Mode mode, bool async, XFile** out_file);
Entry* ResolvePath(std::string path);
private:
std::vector<std::unique_ptr<Device>> devices_;