diff --git a/libxenia.vcxproj b/libxenia.vcxproj index 38887e395..ddb29f09f 100644 --- a/libxenia.vcxproj +++ b/libxenia.vcxproj @@ -205,8 +205,6 @@ - - CompileAsCpp @@ -460,8 +458,6 @@ - - diff --git a/libxenia.vcxproj.filters b/libxenia.vcxproj.filters index f9b82b911..cd19ffd7e 100644 --- a/libxenia.vcxproj.filters +++ b/libxenia.vcxproj.filters @@ -745,12 +745,6 @@ src\xenia\vfs - - src\xenia\vfs - - - src\xenia\vfs - src\xenia\vfs\devices @@ -1452,12 +1446,6 @@ src\xenia\vfs - - src\xenia\vfs - - - src\xenia\vfs - src\xenia\vfs\devices diff --git a/src/xenia/base/filesystem.h b/src/xenia/base/filesystem.h index 11b83cf9a..b4b0b23bf 100644 --- a/src/xenia/base/filesystem.h +++ b/src/xenia/base/filesystem.h @@ -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 ListFiles(const std::wstring& path); diff --git a/src/xenia/base/filesystem_win.cc b/src/xenia/base/filesystem_win.cc index 2ad7346ec..05e99fe8d 100644 --- a/src/xenia/base/filesystem_win.cc +++ b/src/xenia/base/filesystem_win.cc @@ -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 ListFiles(const std::wstring& path) { std::vector result; @@ -66,6 +68,9 @@ std::vector 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); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 07cb8ecc3..466b54319 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -209,6 +209,10 @@ X_STATUS Emulator::LaunchXexFile(std::wstring path) { auto parent_path = xe::find_base_path(path); auto device = std::make_unique(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; diff --git a/src/xenia/kernel/objects/xfile.cc b/src/xenia/kernel/objects/xfile.cc index 99c496c28..565632918 100644 --- a/src/xenia/kernel/objects/xfile.cc +++ b/src/xenia/kernel/objects/xfile.cc @@ -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(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(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) { diff --git a/src/xenia/kernel/objects/xfile.h b/src/xenia/kernel/objects/xfile.h index 967f49a2c..f449294c1 100644 --- a/src/xenia/kernel/objects/xfile.h +++ b/src/xenia/kernel/objects/xfile.h @@ -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 change_time; xe::be allocation_size; xe::be end_of_file; // size in bytes - xe::be attributes; + xe::be attributes; xe::be 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 diff --git a/src/xenia/kernel/objects/xuser_module.cc b/src/xenia/kernel/objects/xuser_module.cc index 8e82db820..bb50d8cac 100644 --- a/src/xenia/kernel/objects/xuser_module.cc +++ b/src/xenia/kernel/objects/xuser_module.cc @@ -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 buffer(file_info.end_of_file); + std::vector 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 file(file_ptr); if (result) { return result; diff --git a/src/xenia/kernel/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl_io.cc index 5101912b1..84aa246b0 100644 --- a/src/xenia/kernel/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl_io.cc @@ -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; object_ref 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(file_ptr); } @@ -618,10 +618,14 @@ dword_result_t NtQueryInformationFile( assert_true(length == 56); auto file_info = file_info_ptr.as(); - 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( 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); diff --git a/src/xenia/vfs/device.cc b/src/xenia/vfs/device.cc index 18f4d1ea8..cb60d44e6 100644 --- a/src/xenia/vfs/device.cc +++ b/src/xenia/vfs/device.cc @@ -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 diff --git a/src/xenia/vfs/device.h b/src/xenia/vfs/device.h index 6c9ca8e17..2d378af25 100644 --- a/src/xenia/vfs/device.h +++ b/src/xenia/vfs/device.h @@ -13,6 +13,7 @@ #include #include +#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 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 root_entry_; }; } // namespace vfs diff --git a/src/xenia/vfs/devices/disc_image_device.cc b/src/xenia/vfs/devices/disc_image_device.cc index 1e57b2baa..5d0768c43 100644 --- a/src/xenia/vfs/devices/disc_image_device.cc +++ b/src/xenia/vfs/devices/disc_image_device.cc @@ -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 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(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(fs_ptr + 20); + state.root_size = xe::load(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(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(p + 0); + uint16_t node_r = xe::load(p + 2); + size_t sector = xe::load(p + 4); + size_t length = xe::load(p + 8); + uint8_t attributes = xe::load(p + 12); + uint8_t name_length = xe::load(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)); + + 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 diff --git a/src/xenia/vfs/devices/disc_image_device.h b/src/xenia/vfs/devices/disc_image_device.h index 1c1151c96..c0ce2ed38 100644 --- a/src/xenia/vfs/devices/disc_image_device.h +++ b/src/xenia/vfs/devices/disc_image_device.h @@ -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 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 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 diff --git a/src/xenia/vfs/devices/disc_image_entry.cc b/src/xenia/vfs/devices/disc_image_entry.cc index 36238dbc5..b05457b96 100644 --- a/src/xenia/vfs/devices/disc_image_entry.cc +++ b/src/xenia/vfs/devices/disc_image_entry.cc @@ -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(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 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); } diff --git a/src/xenia/vfs/devices/disc_image_entry.h b/src/xenia/vfs/devices/disc_image_entry.h index e7f94c2c3..a47612696 100644 --- a/src/xenia/vfs/devices/disc_image_entry.h +++ b/src/xenia/vfs/devices/disc_image_entry.h @@ -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 diff --git a/src/xenia/vfs/devices/disc_image_file.cc b/src/xenia/vfs/devices/disc_image_file.cc index 0629ea475..8a2e706f9 100644 --- a/src/xenia/vfs/devices/disc_image_file.cc +++ b/src/xenia/vfs/devices/disc_image_file.cc @@ -11,43 +11,25 @@ #include -#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; diff --git a/src/xenia/vfs/devices/disc_image_file.h b/src/xenia/vfs/devices/disc_image_file.h index 647c05de3..d01125b67 100644 --- a/src/xenia/vfs/devices/disc_image_file.h +++ b/src/xenia/vfs/devices/disc_image_file.h @@ -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; diff --git a/src/xenia/vfs/devices/host_path_device.cc b/src/xenia/vfs/devices/host_path_device.cc index a51d3232f..e7d81e741 100644 --- a/src/xenia/vfs/devices/host_path_device.cc +++ b/src/xenia/vfs/devices/host_path_device.cc @@ -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 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(root_entry); + PopulateEntry(root_entry); - return std::make_unique(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(child)); + + if (child_info.type == filesystem::FileInfo::Type::kDirectory) { + PopulateEntry(child); + } + } } } // namespace vfs diff --git a/src/xenia/vfs/devices/host_path_device.h b/src/xenia/vfs/devices/host_path_device.h index d5d569d8b..5922bb8a6 100644 --- a/src/xenia/vfs/devices/host_path_device.h +++ b/src/xenia/vfs/devices/host_path_device.h @@ -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 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_; }; diff --git a/src/xenia/vfs/devices/host_path_entry.cc b/src/xenia/vfs/devices/host_path_entry.cc index 8660c9f63..2d18df824 100644 --- a/src/xenia/vfs/devices/host_path_entry.cc +++ b/src/xenia/vfs/devices/host_path_entry.cc @@ -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) { diff --git a/src/xenia/vfs/devices/host_path_entry.h b/src/xenia/vfs/devices/host_path_entry.h index fb38074e1..8cf507a2d 100644 --- a/src/xenia/vfs/devices/host_path_entry.h +++ b/src/xenia/vfs/devices/host_path_entry.h @@ -10,23 +10,23 @@ #ifndef XENIA_VFS_DEVICES_HOST_PATH_ENTRY_H_ #define XENIA_VFS_DEVICES_HOST_PATH_ENTRY_H_ +#include + #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 diff --git a/src/xenia/vfs/devices/host_path_file.cc b/src/xenia/vfs/devices/host_path_file.cc index e20b7522c..87fafd634 100644 --- a/src/xenia/vfs/devices/host_path_file.cc +++ b/src/xenia/vfs/devices/host_path_file.cc @@ -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) { diff --git a/src/xenia/vfs/devices/host_path_file.h b/src/xenia/vfs/devices/host_path_file.h index cb58c2b1f..7e683d8d9 100644 --- a/src/xenia/vfs/devices/host_path_file.h +++ b/src/xenia/vfs/devices/host_path_file.h @@ -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; diff --git a/src/xenia/vfs/devices/stfs_container_device.cc b/src/xenia/vfs/devices/stfs_container_device.cc index f2c4a33be..3c6cd3f8b 100644 --- a/src/xenia/vfs/devices/stfs_container_device.cc +++ b/src/xenia/vfs/devices/stfs_container_device.cc @@ -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((p) + 0) << 16) | \ + ((uint32_t)xe::load_and_swap((p) + 1) << 8) | \ + (uint32_t)xe::load_and_swap((p) + 2)) +#define XEGETUINT24LE(p) \ + (((uint32_t)xe::load((p) + 2) << 16) | \ + ((uint32_t)xe::load((p) + 1) << 8) | \ + (uint32_t)xe::load((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 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(root_entry); + + std::vector 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(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(p + 0x32); + uint32_t file_size = xe::load_and_swap(p + 0x34); + uint32_t update_timestamp = xe::load_and_swap(p + 0x38); + uint32_t access_timestamp = xe::load_and_swap(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)); + } else { + // Lookup and add. + auto parent = all_entries[path_indicator]; + parent->children_.emplace_back(std::unique_ptr(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(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(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(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(p + 0x01); + block_separation = xe::load_and_swap(p + 0x02); + file_table_block_count = xe::load_and_swap(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(p + 0x1C); + total_unallocated_block_count = xe::load_and_swap(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(p + 0x340); + content_type = (STFSContentType)xe::load_and_swap(p + 0x344); + metadata_version = xe::load_and_swap(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(p + 0x34C); + media_id = xe::load_and_swap(p + 0x354); + version = xe::load_and_swap(p + 0x358); + base_version = xe::load_and_swap(p + 0x35C); + title_id = xe::load_and_swap(p + 0x360); + platform = (StfsPlatform)xe::load_and_swap(p + 0x364); + executable_type = xe::load_and_swap(p + 0x365); + disc_number = xe::load_and_swap(p + 0x366); + disc_in_set = xe::load_and_swap(p + 0x367); + save_game_id = xe::load_and_swap(p + 0x368); + std::memcpy(console_id, p + 0x36C, 0x5); + std::memcpy(profile_id, p + 0x371, 0x8); + data_file_count = xe::load_and_swap(p + 0x39D); + data_file_combined_size = xe::load_and_swap(p + 0x3A1); + descriptor_type = (StfsDescriptorType)xe::load_and_swap(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(p + 0x411 + n * 2); + display_descs[n] = xe::load_and_swap(p + 0xD11 + n * 2); + } + for (size_t n = 0; n < 0x80 / 2; n++) { + publisher_name[n] = xe::load_and_swap(p + 0x1611 + n * 2); + title_name[n] = xe::load_and_swap(p + 0x1691 + n * 2); + } + transfer_flags = xe::load_and_swap(p + 0x1711); + thumbnail_image_size = xe::load_and_swap(p + 0x1712); + title_thumbnail_image_size = xe::load_and_swap(p + 0x1716); + std::memcpy(thumbnail_image, p + 0x171A, 0x4000); + std::memcpy(title_thumbnail_image, p + 0x571A, 0x4000); + return true; } } // namespace vfs diff --git a/src/xenia/vfs/devices/stfs_container_device.h b/src/xenia/vfs/devices/stfs_container_device.h index 13d1a9402..0a0567147 100644 --- a/src/xenia/vfs/devices/stfs_container_device.h +++ b/src/xenia/vfs/devices/stfs_container_device.h @@ -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 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 mmap_; - STFS* stfs_; + + StfsPackageType package_type_; + StfsHeader header_; + uint32_t table_size_shift_; }; } // namespace vfs diff --git a/src/xenia/vfs/devices/stfs_container_entry.cc b/src/xenia/vfs/devices/stfs_container_entry.cc index 2a963f3fd..61d35c43f 100644 --- a/src/xenia/vfs/devices/stfs_container_entry.cc +++ b/src/xenia/vfs/devices/stfs_container_entry.cc @@ -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(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); diff --git a/src/xenia/vfs/devices/stfs_container_entry.h b/src/xenia/vfs/devices/stfs_container_entry.h index 7a4660403..55321957b 100644 --- a/src/xenia/vfs/devices/stfs_container_entry.h +++ b/src/xenia/vfs/devices/stfs_container_entry.h @@ -11,38 +11,41 @@ #define XENIA_VFS_DEVICES_STFS_CONTAINER_ENTRY_H_ #include -#include #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& 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 block_list_; }; } // namespace vfs diff --git a/src/xenia/vfs/devices/stfs_container_file.cc b/src/xenia/vfs/devices/stfs_container_file.cc index 9fe9b878d..cdbabc887 100644 --- a/src/xenia/vfs/devices/stfs_container_file.cc +++ b/src/xenia/vfs/devices/stfs_container_file.cc @@ -11,56 +11,36 @@ #include -#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) { diff --git a/src/xenia/vfs/devices/stfs_container_file.h b/src/xenia/vfs/devices/stfs_container_file.h index c273d4e30..a39f1107c 100644 --- a/src/xenia/vfs/devices/stfs_container_file.h +++ b/src/xenia/vfs/devices/stfs_container_file.h @@ -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; diff --git a/src/xenia/vfs/entry.cc b/src/xenia/vfs/entry.cc index e03f5b24e..61895e90d 100644 --- a/src/xenia/vfs/entry.cc +++ b/src/xenia/vfs/entry.cc @@ -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 diff --git a/src/xenia/vfs/entry.h b/src/xenia/vfs/entry.h index e04a9f3c2..1eaf03d75 100644 --- a/src/xenia/vfs/entry.h +++ b/src/xenia/vfs/entry.h @@ -13,15 +13,15 @@ #include #include +#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> children_; }; } // namespace vfs diff --git a/src/xenia/vfs/gdfx.cc b/src/xenia/vfs/gdfx.cc deleted file mode 100644 index c612d3eca..000000000 --- a/src/xenia/vfs/gdfx.cc +++ /dev/null @@ -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::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::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::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(fs_ptr + 20); - state.root_size = xe::load(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(p + 0); - uint16_t node_r = xe::load(p + 2); - size_t sector = xe::load(p + 4); - size_t length = xe::load(p + 8); - uint8_t attributes = xe::load(p + 12); - uint8_t name_length = xe::load(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 diff --git a/src/xenia/vfs/gdfx.h b/src/xenia/vfs/gdfx.h deleted file mode 100644 index 8cbc8cd27..000000000 --- a/src/xenia/vfs/gdfx.h +++ /dev/null @@ -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 - -#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 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_ diff --git a/src/xenia/vfs/stfs.cc b/src/xenia/vfs/stfs.cc deleted file mode 100644 index 074b003fc..000000000 --- a/src/xenia/vfs/stfs.cc +++ /dev/null @@ -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 - -#include "xenia/base/logging.h" - -namespace xe { -namespace vfs { - -#define XEGETUINT24BE(p) \ - (((uint32_t)xe::load_and_swap((p) + 0) << 16) | \ - ((uint32_t)xe::load_and_swap((p) + 1) << 8) | \ - (uint32_t)xe::load_and_swap((p) + 2)) -#define XEGETUINT24LE(p) \ - (((uint32_t)xe::load((p) + 2) << 16) | \ - ((uint32_t)xe::load((p) + 1) << 8) | \ - (uint32_t)xe::load((p) + 0)) - -bool STFSVolumeDescriptor::Read(const uint8_t* p) { - descriptor_size = xe::load_and_swap(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(p + 0x01); - block_separation = xe::load_and_swap(p + 0x02); - file_table_block_count = xe::load_and_swap(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(p + 0x1C); - total_unallocated_block_count = xe::load_and_swap(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(p + 0x340); - content_type = (STFSContentType)xe::load_and_swap(p + 0x344); - metadata_version = xe::load_and_swap(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(p + 0x34C); - media_id = xe::load_and_swap(p + 0x354); - version = xe::load_and_swap(p + 0x358); - base_version = xe::load_and_swap(p + 0x35C); - title_id = xe::load_and_swap(p + 0x360); - platform = (STFSPlatform)xe::load_and_swap(p + 0x364); - executable_type = xe::load_and_swap(p + 0x365); - disc_number = xe::load_and_swap(p + 0x366); - disc_in_set = xe::load_and_swap(p + 0x367); - save_game_id = xe::load_and_swap(p + 0x368); - memcpy(console_id, p + 0x36C, 0x5); - memcpy(profile_id, p + 0x371, 0x8); - data_file_count = xe::load_and_swap(p + 0x39D); - data_file_combined_size = xe::load_and_swap(p + 0x3A1); - descriptor_type = (STFSDescriptorType)xe::load_and_swap(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(p + 0x411 + n * 2); - display_descs[n] = xe::load_and_swap(p + 0xD11 + n * 2); - } - for (size_t n = 0; n < 0x80 / 2; n++) { - publisher_name[n] = xe::load_and_swap(p + 0x1611 + n * 2); - title_name[n] = xe::load_and_swap(p + 0x1691 + n * 2); - } - transfer_flags = xe::load_and_swap(p + 0x1711); - thumbnail_image_size = xe::load_and_swap(p + 0x1712); - title_thumbnail_image_size = xe::load_and_swap(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 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(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(p + 0x32); - uint32_t file_size = xe::load_and_swap(p + 0x34); - uint32_t update_timestamp = xe::load_and_swap(p + 0x38); - uint32_t access_timestamp = xe::load_and_swap(p + 0x3C); - p += 0x40; - - auto entry = std::make_unique(); - 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(record_data + 0x14); - uint32_t next_block_index = XEGETUINT24BE(record_data + 0x15); - return {next_block_index, info}; -} - -} // namespace vfs -} // namespace xe diff --git a/src/xenia/vfs/stfs.h b/src/xenia/vfs/stfs.h deleted file mode 100644 index e41151c88..000000000 --- a/src/xenia/vfs/stfs.h +++ /dev/null @@ -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 -#include - -#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> 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 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 root_entry_; -}; - -} // namespace vfs -} // namespace xe - -#endif // XENIA_VFS_STFS_H_ diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index c5ab0c39f..600f5aaca 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -45,7 +45,7 @@ bool VirtualFileSystem::UnregisterSymbolicLink(std::string path) { return true; } -std::unique_ptr 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 VirtualFileSystem::ResolvePath(const std::string& path) { return nullptr; } -X_STATUS VirtualFileSystem::Open(std::unique_ptr 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 diff --git a/src/xenia/vfs/virtual_file_system.h b/src/xenia/vfs/virtual_file_system.h index e8c50af1c..b44b374e1 100644 --- a/src/xenia/vfs/virtual_file_system.h +++ b/src/xenia/vfs/virtual_file_system.h @@ -31,9 +31,7 @@ class VirtualFileSystem { bool RegisterSymbolicLink(std::string path, std::string target); bool UnregisterSymbolicLink(std::string path); - std::unique_ptr ResolvePath(const std::string& path); - X_STATUS Open(std::unique_ptr entry, KernelState* kernel_state, - Mode mode, bool async, XFile** out_file); + Entry* ResolvePath(std::string path); private: std::vector> devices_;