parent
1ac19f1b08
commit
83872d8e8f
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
Loading…
Reference in New Issue