STFS loading... xex's load, but files seem broken. Ugh.
This commit is contained in:
parent
69320ed94b
commit
6b633e4e28
|
@ -76,6 +76,10 @@ XEFORCEINLINE double XESWAPF64BE(double value) {
|
|||
#define XEGETUINT8BE(p) ( (uint8_t)(*(p)))
|
||||
#define XEGETINT16BE(p) ( (int16_t)XESWAP16BE(*(uint16_t*)(p)))
|
||||
#define XEGETUINT16BE(p) ((uint16_t)XESWAP16BE(*(uint16_t*)(p)))
|
||||
#define XEGETUINT24BE(p) \
|
||||
(((uint32_t)XEGETUINT8BE((p) + 0) << 16) | \
|
||||
((uint32_t)XEGETUINT8BE((p) + 1) << 8) | \
|
||||
(uint32_t)XEGETUINT8BE((p) + 2))
|
||||
#define XEGETINT32BE(p) ( (int32_t)XESWAP32BE(*(uint32_t*)(p)))
|
||||
#define XEGETUINT32BE(p) ((uint32_t)XESWAP32BE(*(uint32_t*)(p)))
|
||||
#define XEGETINT64BE(p) ( (int64_t)XESWAP64BE(*(uint64_t*)(p)))
|
||||
|
@ -84,6 +88,10 @@ XEFORCEINLINE double XESWAPF64BE(double value) {
|
|||
#define XEGETUINT8LE(p) ( (uint8_t)(*(p)))
|
||||
#define XEGETINT16LE(p) ( (int16_t)XESWAP16LE(*(uint16_t*)(p)))
|
||||
#define XEGETUINT16LE(p) ((uint16_t)XESWAP16LE(*(uint16_t*)(p)))
|
||||
#define XEGETUINT24LE(p) \
|
||||
(((uint32_t)XEGETUINT8LE((p) + 2) << 16) | \
|
||||
((uint32_t)XEGETUINT8LE((p) + 1) << 8) | \
|
||||
(uint32_t)XEGETUINT8LE((p) + 0))
|
||||
#define XEGETINT32LE(p) ( (int32_t)XESWAP32LE(*(uint32_t*)(p)))
|
||||
#define XEGETUINT32LE(p) ((uint32_t)XESWAP32LE(*(uint32_t*)(p)))
|
||||
#define XEGETINT64LE(p) ( (int64_t)XESWAP64LE(*(uint64_t*)(p)))
|
||||
|
|
|
@ -231,3 +231,28 @@ X_STATUS Emulator::LaunchDiscImage(const xechar_t* path) {
|
|||
// Launch the game.
|
||||
return xboxkrnl_->LaunchModule("game:\\default.xex");
|
||||
}
|
||||
|
||||
X_STATUS Emulator::LaunchSTFSTitle(const xechar_t* path) {
|
||||
int result_code = 0;
|
||||
|
||||
// TODO(benvanik): figure out paths.
|
||||
|
||||
// Register the disc image in the virtual filesystem.
|
||||
result_code = file_system_->RegisterSTFSContainerDevice(
|
||||
"\\Device\\Cdrom0", path);
|
||||
if (result_code) {
|
||||
XELOGE("Unable to mount STFS container");
|
||||
return result_code;
|
||||
}
|
||||
|
||||
// Create symlinks to the device.
|
||||
file_system_->CreateSymbolicLink(
|
||||
"game:",
|
||||
"\\Device\\Cdrom0");
|
||||
file_system_->CreateSymbolicLink(
|
||||
"d:",
|
||||
"\\Device\\Cdrom0");
|
||||
|
||||
// Launch the game.
|
||||
return xboxkrnl_->LaunchModule("game:\\default.xex");
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
// TODO(benvanik): raw binary.
|
||||
X_STATUS LaunchXexFile(const xechar_t* path);
|
||||
X_STATUS LaunchDiscImage(const xechar_t* path);
|
||||
X_STATUS LaunchSTFSTitle(const xechar_t* path);
|
||||
|
||||
private:
|
||||
xechar_t command_line_[XE_MAX_PATH];
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
|
||||
virtual bool can_map() { return true; }
|
||||
virtual MemoryMapping* CreateMemoryMapping(
|
||||
xe_file_mode file_mode, const size_t offset, const size_t length);
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ X_STATUS DiscImageFile::QueryInfo(XFileInfo* out_info) {
|
|||
return entry_->QueryInfo(out_info);
|
||||
}
|
||||
|
||||
X_STATUS DiscImageFile::QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) {
|
||||
X_STATUS DiscImageFile::QueryDirectory(
|
||||
XDirectoryInfo* out_info, size_t length, bool restart) {
|
||||
XEASSERTALWAYS();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ public:
|
|||
virtual ~DiscImageFile();
|
||||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info,
|
||||
size_t length, bool restart);
|
||||
|
||||
protected:
|
||||
virtual X_STATUS ReadSync(
|
||||
|
|
|
@ -67,12 +67,14 @@ X_STATUS HostPathEntry::QueryInfo(XFileInfo* out_info) {
|
|||
out_info->last_write_time = COMBINE_TIME(data.ftLastWriteTime);
|
||||
out_info->change_time = COMBINE_TIME(data.ftLastWriteTime);
|
||||
out_info->allocation_size = 4096;
|
||||
out_info->file_length = ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
|
||||
out_info->file_length =
|
||||
((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
|
||||
out_info->attributes = (X_FILE_ATTRIBUTES)data.dwFileAttributes;
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
X_STATUS HostPathEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart) {
|
||||
X_STATUS HostPathEntry::QueryDirectory(
|
||||
XDirectoryInfo* out_info, size_t length, bool restart) {
|
||||
XEASSERTNOTNULL(out_info);
|
||||
|
||||
if (length < sizeof(XDirectoryInfo)) {
|
||||
|
@ -121,7 +123,8 @@ X_STATUS HostPathEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length,
|
|||
|
||||
do {
|
||||
size_t file_name_length = wcslen(ffd.cFileName);
|
||||
if (((uint8_t*)&((XDirectoryInfo*)current_buf)->file_name[0]) + wcslen(ffd.cFileName) > end) {
|
||||
if (((uint8_t*)&((XDirectoryInfo*)current_buf)->file_name[0]) +
|
||||
wcslen(ffd.cFileName) > end) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -131,14 +134,17 @@ X_STATUS HostPathEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length,
|
|||
current->last_access_time = COMBINE_TIME(ffd.ftLastAccessTime);
|
||||
current->last_write_time = COMBINE_TIME(ffd.ftLastWriteTime);
|
||||
current->change_time = COMBINE_TIME(ffd.ftLastWriteTime);
|
||||
current->end_of_file = ((uint64_t)ffd.nFileSizeHigh << 32) | ffd.nFileSizeLow;
|
||||
current->end_of_file =
|
||||
((uint64_t)ffd.nFileSizeHigh << 32) | ffd.nFileSizeLow;
|
||||
current->allocation_size = 4096;
|
||||
current->attributes = (X_FILE_ATTRIBUTES)ffd.dwFileAttributes;
|
||||
|
||||
// TODO: I am pretty sure you need to prefix the file paths with full path, not just the name.
|
||||
// TODO: I am pretty sure you need to prefix the file paths with full path,
|
||||
// not just the name.
|
||||
current->file_name_length = (uint32_t)file_name_length;
|
||||
for (size_t i = 0; i < file_name_length; ++i) {
|
||||
current->file_name[i] = ffd.cFileName[i] < 256 ? (char)ffd.cFileName[i] : '?';
|
||||
current->file_name[i] =
|
||||
ffd.cFileName[i] < 256 ? (char)ffd.cFileName[i] : '?';
|
||||
}
|
||||
|
||||
auto next_buf = (((uint8_t*)¤t->file_name[0]) + file_name_length);
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart);
|
||||
|
||||
virtual bool can_map() { return true; }
|
||||
virtual MemoryMapping* CreateMemoryMapping(
|
||||
xe_file_mode file_mode, const size_t offset, const size_t length);
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@ public:
|
|||
virtual ~HostPathFile();
|
||||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, bool restart);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info,
|
||||
size_t length, bool restart);
|
||||
|
||||
protected:
|
||||
virtual X_STATUS ReadSync(
|
||||
|
|
|
@ -13,5 +13,11 @@
|
|||
'host_path_entry.h',
|
||||
'host_path_file.cc',
|
||||
'host_path_file.h',
|
||||
'stfs_container_device.cc',
|
||||
'stfs_container_device.h',
|
||||
'stfs_container_entry.cc',
|
||||
'stfs_container_entry.h',
|
||||
'stfs_container_file.cc',
|
||||
'stfs_container_file.h',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <xenia/kernel/fs/devices/stfs_container_device.h>
|
||||
|
||||
#include <xenia/kernel/fs/stfs.h>
|
||||
#include <xenia/kernel/fs/devices/stfs_container_entry.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::kernel;
|
||||
using namespace xe::kernel::fs;
|
||||
|
||||
|
||||
|
||||
STFSContainerDevice::STFSContainerDevice(
|
||||
const char* path, const xechar_t* local_path) :
|
||||
Device(path) {
|
||||
local_path_ = xestrdup(local_path);
|
||||
mmap_ = NULL;
|
||||
stfs_ = NULL;
|
||||
}
|
||||
|
||||
STFSContainerDevice::~STFSContainerDevice() {
|
||||
delete stfs_;
|
||||
xe_mmap_release(mmap_);
|
||||
xe_free(local_path_);
|
||||
}
|
||||
|
||||
int STFSContainerDevice::Init() {
|
||||
mmap_ = xe_mmap_open(kXEFileModeRead, local_path_, 0, 0);
|
||||
if (!mmap_) {
|
||||
XELOGE("STFS container could not be mapped");
|
||||
return 1;
|
||||
}
|
||||
|
||||
stfs_ = new STFS(mmap_);
|
||||
STFS::Error error = stfs_->Load();
|
||||
if (error != STFS::kSuccess) {
|
||||
XELOGE("STFS init failed: %d", error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
stfs_->Dump();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
XELOGFS("STFSContainerDevice::ResolvePath(%s)", path);
|
||||
|
||||
STFSEntry* stfs_entry = stfs_->root_entry();
|
||||
|
||||
// Walk the path, one separator at a time.
|
||||
// We copy it into the buffer and shift it left over and over.
|
||||
char remaining[XE_MAX_PATH];
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), path));
|
||||
while (remaining[0]) {
|
||||
char* next_slash = xestrchra(remaining, '\\');
|
||||
if (next_slash == remaining) {
|
||||
// Leading slash - shift
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), remaining + 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make the buffer just the name.
|
||||
if (next_slash) {
|
||||
*next_slash = 0;
|
||||
}
|
||||
|
||||
// Look up in the entry.
|
||||
stfs_entry = stfs_entry->GetChild(remaining);
|
||||
if (!stfs_entry) {
|
||||
// Not found.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Shift the buffer down, unless we are at the end.
|
||||
if (!next_slash) {
|
||||
break;
|
||||
}
|
||||
XEIGNORE(xestrcpya(remaining, XECOUNT(remaining), next_slash + 1));
|
||||
}
|
||||
|
||||
Entry::Type type = stfs_entry->attributes & X_FILE_ATTRIBUTE_DIRECTORY ?
|
||||
Entry::kTypeDirectory : Entry::kTypeFile;
|
||||
return new STFSContainerEntry(
|
||||
type, this, path, mmap_, stfs_entry);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_FS_DEVICES_STFS_CONTAINER_DEVICE_H_
|
||||
#define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_DEVICE_H_
|
||||
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core.h>
|
||||
|
||||
#include <xenia/kernel/fs/device.h>
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace fs {
|
||||
|
||||
|
||||
class STFS;
|
||||
|
||||
|
||||
class STFSContainerDevice : public Device {
|
||||
public:
|
||||
STFSContainerDevice(const char* path, const xechar_t* local_path);
|
||||
virtual ~STFSContainerDevice();
|
||||
|
||||
int Init();
|
||||
|
||||
virtual Entry* ResolvePath(const char* path);
|
||||
|
||||
private:
|
||||
xechar_t* local_path_;
|
||||
xe_mmap_ref mmap_;
|
||||
STFS* stfs_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace fs
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_DEVICE_H_
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <xenia/kernel/fs/devices/stfs_container_entry.h>
|
||||
|
||||
#include <xenia/kernel/fs/stfs.h>
|
||||
#include <xenia/kernel/fs/devices/stfs_container_file.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::kernel;
|
||||
using namespace xe::kernel::fs;
|
||||
|
||||
|
||||
STFSContainerEntry::STFSContainerEntry(
|
||||
Type type, Device* device, const char* path,
|
||||
xe_mmap_ref mmap, STFSEntry* stfs_entry) :
|
||||
stfs_entry_(stfs_entry),
|
||||
Entry(type, device, path) {
|
||||
mmap_ = xe_mmap_retain(mmap);
|
||||
}
|
||||
|
||||
STFSContainerEntry::~STFSContainerEntry() {
|
||||
xe_mmap_release(mmap_);
|
||||
}
|
||||
|
||||
X_STATUS STFSContainerEntry::QueryInfo(XFileInfo* out_info) {
|
||||
XEASSERTNOTNULL(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 = 4096;
|
||||
out_info->file_length = stfs_entry_->size;
|
||||
out_info->attributes = stfs_entry_->attributes;
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
X_STATUS STFSContainerEntry::Open(
|
||||
KernelState* kernel_state,
|
||||
uint32_t desired_access, bool async,
|
||||
XFile** out_file) {
|
||||
*out_file = new STFSContainerFile(kernel_state, desired_access, this);
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_
|
||||
#define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_
|
||||
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core.h>
|
||||
|
||||
#include <xenia/kernel/fs/entry.h>
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace fs {
|
||||
|
||||
class STFSEntry;
|
||||
|
||||
|
||||
class STFSContainerEntry : public Entry {
|
||||
public:
|
||||
STFSContainerEntry(Type type, Device* device, const char* path,
|
||||
xe_mmap_ref mmap, STFSEntry* stfs_entry);
|
||||
virtual ~STFSContainerEntry();
|
||||
|
||||
xe_mmap_ref mmap() const { return mmap_; }
|
||||
STFSEntry* stfs_entry() const { return stfs_entry_; }
|
||||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
|
||||
virtual X_STATUS Open(
|
||||
KernelState* kernel_state,
|
||||
uint32_t desired_access, bool async,
|
||||
XFile** out_file);
|
||||
|
||||
private:
|
||||
xe_mmap_ref mmap_;
|
||||
STFSEntry* stfs_entry_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace fs
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <xenia/kernel/fs/devices/stfs_container_file.h>
|
||||
|
||||
#include <xenia/kernel/fs/stfs.h>
|
||||
#include <xenia/kernel/fs/devices/stfs_container_entry.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::kernel;
|
||||
using namespace xe::kernel::fs;
|
||||
|
||||
|
||||
STFSContainerFile::STFSContainerFile(
|
||||
KernelState* kernel_state, uint32_t desired_access,
|
||||
STFSContainerEntry* entry) :
|
||||
entry_(entry),
|
||||
XFile(kernel_state, desired_access) {
|
||||
}
|
||||
|
||||
STFSContainerFile::~STFSContainerFile() {
|
||||
}
|
||||
|
||||
X_STATUS STFSContainerFile::QueryInfo(XFileInfo* out_info) {
|
||||
return entry_->QueryInfo(out_info);
|
||||
}
|
||||
|
||||
X_STATUS STFSContainerFile::QueryDirectory(
|
||||
XDirectoryInfo* out_info, size_t length, bool restart) {
|
||||
XEASSERTALWAYS();
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
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();
|
||||
xe_mmap_ref mmap = entry_->mmap();
|
||||
uint8_t* map_ptr = xe_mmap_get_addr(mmap);
|
||||
|
||||
// Each block is 4096.
|
||||
// Blocks may not be sequential, so we need to read by blocks and handle the
|
||||
// offsets.
|
||||
size_t real_length = MIN(buffer_length, stfs_entry->size - byte_offset);
|
||||
size_t start_block = byte_offset / 4096;
|
||||
size_t end_block = MIN(
|
||||
stfs_entry->block_list.size(),
|
||||
(size_t)ceil((byte_offset + real_length) / 4096.0));
|
||||
uint8_t* dest_ptr = (uint8_t*)buffer;
|
||||
for (size_t n = start_block; n < end_block; n++) {
|
||||
auto& record = stfs_entry->block_list[n];
|
||||
size_t offset = record.offset;
|
||||
size_t read_length = record.length;
|
||||
xe_copy_memory(dest_ptr, buffer_length - (dest_ptr - (uint8_t*)buffer),
|
||||
map_ptr + offset, read_length);
|
||||
dest_ptr += read_length;
|
||||
}
|
||||
*out_bytes_read = real_length;
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_FS_DEVICES_STFS_CONTAINER_FILE_H_
|
||||
#define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_FILE_H_
|
||||
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core.h>
|
||||
|
||||
#include <xenia/kernel/objects/xfile.h>
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace fs {
|
||||
|
||||
class STFSContainerEntry;
|
||||
|
||||
|
||||
class STFSContainerFile : public XFile {
|
||||
public:
|
||||
STFSContainerFile(KernelState* kernel_state, uint32_t desired_access,
|
||||
STFSContainerEntry* entry);
|
||||
virtual ~STFSContainerFile();
|
||||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info);
|
||||
virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info,
|
||||
size_t length, bool restart);
|
||||
|
||||
protected:
|
||||
virtual X_STATUS ReadSync(
|
||||
void* buffer, size_t buffer_length, size_t byte_offset,
|
||||
size_t* out_bytes_read);
|
||||
|
||||
private:
|
||||
STFSContainerEntry* entry_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace fs
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_FILE_H_
|
|
@ -60,8 +60,11 @@ public:
|
|||
|
||||
virtual X_STATUS QueryInfo(XFileInfo* out_info) = 0;
|
||||
|
||||
virtual bool can_map() { return false; }
|
||||
virtual MemoryMapping* CreateMemoryMapping(
|
||||
xe_file_mode file_mode, const size_t offset, const size_t length) = 0;
|
||||
xe_file_mode file_mode, const size_t offset, const size_t length) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
virtual X_STATUS Open(
|
||||
KernelState* kernel_state,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <xenia/kernel/fs/devices/disc_image_device.h>
|
||||
#include <xenia/kernel/fs/devices/host_path_device.h>
|
||||
#include <xenia/kernel/fs/devices/stfs_container_device.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
|
@ -52,6 +53,15 @@ int FileSystem::RegisterDiscImageDevice(
|
|||
return RegisterDevice(path, device);
|
||||
}
|
||||
|
||||
int FileSystem::RegisterSTFSContainerDevice(
|
||||
const char* path, const xechar_t* local_path) {
|
||||
STFSContainerDevice* device = new STFSContainerDevice(path, local_path);
|
||||
if (device->Init()) {
|
||||
return 1;
|
||||
}
|
||||
return RegisterDevice(path, device);
|
||||
}
|
||||
|
||||
int FileSystem::CreateSymbolicLink(const char* path, const char* target) {
|
||||
symlinks_.insert(std::pair<const char*, const char*>(
|
||||
xestrdupa(path),
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
int RegisterDevice(const char* path, Device* device);
|
||||
int RegisterHostPathDevice(const char* path, const xechar_t* local_path);
|
||||
int RegisterDiscImageDevice(const char* path, const xechar_t* local_path);
|
||||
int RegisterSTFSContainerDevice(const char* path, const xechar_t* local_path);
|
||||
|
||||
int CreateSymbolicLink(const char* path, const char* target);
|
||||
int DeleteSymbolicLink(const char* path);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
'filesystem.h',
|
||||
'gdfx.cc',
|
||||
'gdfx.h',
|
||||
'stfs.cc',
|
||||
'stfs.h',
|
||||
],
|
||||
|
||||
'includes': [
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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/kernel/fs/stfs.h>
|
||||
|
||||
|
||||
using namespace xe;
|
||||
using namespace xe::kernel;
|
||||
using namespace xe::kernel::fs;
|
||||
|
||||
|
||||
bool STFSVolumeDescriptor::Read(const uint8_t* p) {
|
||||
descriptor_size = XEGETUINT8BE(p + 0x00);
|
||||
if (descriptor_size != 0x24) {
|
||||
XELOGE("STFS volume descriptor size mismatch, expected 0x24 but got 0x%X",
|
||||
descriptor_size);
|
||||
return false;
|
||||
}
|
||||
reserved = XEGETUINT8BE(p + 0x01);
|
||||
block_separation = XEGETUINT8BE(p + 0x02);
|
||||
file_table_block_count = XEGETUINT16BE(p + 0x03);
|
||||
file_table_block_number = XEGETUINT24BE(p + 0x05);
|
||||
xe_copy_struct(top_hash_table_hash, p + 0x08, 0x14);
|
||||
total_allocated_block_count = XEGETUINT32BE(p + 0x1C);
|
||||
total_unallocated_block_count = XEGETUINT32BE(p + 0x20);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
bool STFSHeader::Read(const uint8_t* p) {
|
||||
xe_copy_struct(license_entries, p + 0x22C, 0x100);
|
||||
xe_copy_struct(header_hash, p + 0x32C, 0x14);
|
||||
header_size = XEGETUINT32BE(p + 0x340);
|
||||
content_type = (STFSContentType)XEGETUINT32BE(p + 0x344);
|
||||
metadata_version = XEGETUINT32BE(p + 0x348);
|
||||
if (metadata_version > 1) {
|
||||
XELOGE("STFSContainer doesn't support version %d yet", metadata_version);
|
||||
return false;
|
||||
}
|
||||
content_size = XEGETUINT32BE(p + 0x34C);
|
||||
media_id = XEGETUINT32BE(p + 0x354);
|
||||
version = XEGETUINT32BE(p + 0x358);
|
||||
base_version = XEGETUINT32BE(p + 0x35C);
|
||||
title_id = XEGETUINT32BE(p + 0x360);
|
||||
platform = (STFSPlatform)XEGETUINT8BE(p + 0x364);
|
||||
executable_type = XEGETUINT8BE(p + 0x365);
|
||||
disc_number = XEGETUINT8BE(p + 0x366);
|
||||
disc_in_set = XEGETUINT8BE(p + 0x367);
|
||||
save_game_id = XEGETUINT32BE(p + 0x368);
|
||||
xe_copy_struct(console_id, p + 0x36C, 0x5);
|
||||
xe_copy_struct(profile_id, p + 0x371, 0x8);
|
||||
data_file_count = XEGETUINT32BE(p + 0x39D);
|
||||
data_file_combined_size = XEGETUINT64BE(p + 0x3A1);
|
||||
descriptor_type = (STFSDescriptorType)XEGETUINT8BE(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;
|
||||
}
|
||||
xe_copy_struct(device_id, p + 0x3FD, 0x14);
|
||||
for (size_t n = 0; n < 0x900 / 2; n++) {
|
||||
display_names[n] = XEGETUINT16BE(p + 0x411 + n * 2);
|
||||
display_descs[n] = XEGETUINT16BE(p + 0xD11 + n * 2);
|
||||
}
|
||||
for (size_t n = 0; n < 0x80 / 2; n++) {
|
||||
publisher_name[n] = XEGETUINT16BE(p + 0x1611 + n * 2);
|
||||
title_name[n] = XEGETUINT16BE(p + 0x1691 + n * 2);
|
||||
}
|
||||
transfer_flags = XEGETUINT8BE(p + 0x1711);
|
||||
thumbnail_image_size = XEGETUINT32BE(p + 0x1712);
|
||||
title_thumbnail_image_size = XEGETUINT32BE(p + 0x1716);
|
||||
xe_copy_struct(thumbnail_image, p + 0x171A, 0x4000);
|
||||
xe_copy_struct(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() {
|
||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||
delete *it;
|
||||
}
|
||||
}
|
||||
|
||||
STFSEntry* STFSEntry::GetChild(const char* name) {
|
||||
// TODO(benvanik): a faster search
|
||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||
STFSEntry* entry = *it;
|
||||
if (xestrcasecmpa(entry->name.c_str(), name) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void STFSEntry::Dump(int indent) {
|
||||
printf("%s%s\n", std::string(indent, ' ').c_str(), name.c_str());
|
||||
for (auto it = children.begin(); it != children.end(); ++it) {
|
||||
STFSEntry* entry = *it;
|
||||
entry->Dump(indent + 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
STFS::STFS(xe_mmap_ref mmap) {
|
||||
mmap_ = xe_mmap_retain(mmap);
|
||||
|
||||
root_entry_ = NULL;
|
||||
}
|
||||
|
||||
STFS::~STFS() {
|
||||
delete root_entry_;
|
||||
|
||||
xe_mmap_release(mmap_);
|
||||
}
|
||||
|
||||
STFSEntry* STFS::root_entry() {
|
||||
return root_entry_;
|
||||
}
|
||||
|
||||
STFS::Error STFS::Load() {
|
||||
Error result = kErrorOutOfMemory;
|
||||
|
||||
uint8_t* map_ptr = (uint8_t*)xe_mmap_get_addr(mmap_);
|
||||
size_t map_size = xe_mmap_get_length(mmap_);
|
||||
|
||||
result = ReadHeaderAndVerify(map_ptr);
|
||||
XEEXPECTZERO(result);
|
||||
|
||||
result = ReadAllEntries(map_ptr);
|
||||
XEEXPECTZERO(result);
|
||||
|
||||
result = kSuccess;
|
||||
XECLEANUP:
|
||||
return result;
|
||||
}
|
||||
|
||||
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_ = new STFSEntry();
|
||||
root_entry_->offset = 0;
|
||||
root_entry_->size = 0;
|
||||
root_entry_->name = "";
|
||||
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 = XEGETUINT8BE(p + 0x28);
|
||||
uint32_t allocated_block_count = XEGETUINT24LE(p + 0x29);
|
||||
uint32_t start_block_index = XEGETUINT24LE(p + 0x2F);
|
||||
uint16_t path_indicator = XEGETUINT16BE(p + 0x32);
|
||||
uint32_t file_size = XEGETUINT32BE(p + 0x34);
|
||||
uint32_t update_timestamp = XEGETUINT32BE(p + 0x38);
|
||||
uint32_t access_timestamp = XEGETUINT32BE(p + 0x3C);
|
||||
p += 0x40;
|
||||
|
||||
STFSEntry* entry = new STFSEntry();
|
||||
entry->name = std::string((char*)filename, filename_length_flags & 0x3F);
|
||||
entry->name.append(1, '\0');
|
||||
// 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);
|
||||
|
||||
const uint8_t* p = map_ptr + entry->offset;
|
||||
|
||||
if (path_indicator == 0xFFFF) {
|
||||
// Root entry.
|
||||
root_entry_->children.push_back(entry);
|
||||
} else {
|
||||
// Lookup and add.
|
||||
auto parent = entries[path_indicator];
|
||||
parent->children.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 = MIN(0x1000, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = XEGETUINT8BE(record_data + 0x14);
|
||||
uint32_t next_block_index = XEGETUINT24BE(record_data + 0x15);
|
||||
return{ next_block_index, info };
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_KERNEL_FS_STFS_H_
|
||||
#define XENIA_KERNEL_FS_STFS_H_
|
||||
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <xenia/xbox.h>
|
||||
#include <xenia/kernel/fs/entry.h>
|
||||
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace fs {
|
||||
|
||||
|
||||
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();
|
||||
~STFSEntry();
|
||||
|
||||
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;
|
||||
|
||||
std::vector<STFSEntry*> 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(xe_mmap_ref mmap);
|
||||
virtual ~STFS();
|
||||
|
||||
const STFSHeader* header() const { return &header_; }
|
||||
STFSEntry* root_entry();
|
||||
|
||||
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);
|
||||
|
||||
xe_mmap_ref mmap_;
|
||||
|
||||
STFSPackageType package_type_;
|
||||
STFSHeader header_;
|
||||
uint32_t table_size_shift_;
|
||||
STFSEntry* root_entry_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace fs
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
|
||||
#endif // XENIA_KERNEL_FS_STFS_H_
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <xenia/emulator.h>
|
||||
#include <xenia/cpu/cpu.h>
|
||||
#include <xenia/kernel/objects/xfile.h>
|
||||
#include <xenia/kernel/objects/xthread.h>
|
||||
|
||||
|
||||
|
@ -37,32 +38,66 @@ const xe_xex2_header_t* XUserModule::xex_header() {
|
|||
}
|
||||
|
||||
X_STATUS XUserModule::LoadFromFile(const char* path) {
|
||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||
XFile* file = NULL;
|
||||
uint8_t* buffer = 0;
|
||||
|
||||
// Resolve the file to open.
|
||||
// TODO(benvanik): make this code shared?
|
||||
fs::Entry* fs_entry = kernel_state()->file_system()->ResolvePath(path);
|
||||
if (!fs_entry) {
|
||||
XELOGE("File not found: %s", path);
|
||||
return X_STATUS_NO_SUCH_FILE;
|
||||
result = X_STATUS_NO_SUCH_FILE;
|
||||
XEFAIL();
|
||||
}
|
||||
if (fs_entry->type() != fs::Entry::kTypeFile) {
|
||||
XELOGE("Invalid file type: %s", path);
|
||||
return X_STATUS_NO_SUCH_FILE;
|
||||
result = X_STATUS_NO_SUCH_FILE;
|
||||
XEFAIL();
|
||||
}
|
||||
|
||||
// Map into memory.
|
||||
fs::MemoryMapping* mmap = fs_entry->CreateMemoryMapping(kXEFileModeRead, 0, 0);
|
||||
if (!mmap) {
|
||||
return X_STATUS_UNSUCCESSFUL;
|
||||
// If the FS supports mapping, map the file in and load from that.
|
||||
if (fs_entry->can_map()) {
|
||||
// Map.
|
||||
fs::MemoryMapping* mmap = fs_entry->CreateMemoryMapping(kXEFileModeRead, 0, 0);
|
||||
XEEXPECTNOTNULL(mmap);
|
||||
|
||||
// Load the module.
|
||||
result = LoadFromMemory(mmap->address(), mmap->length());
|
||||
|
||||
// Unmap memory and cleanup.
|
||||
delete mmap;
|
||||
} else {
|
||||
XFileInfo file_info;
|
||||
result = fs_entry->QueryInfo(&file_info);
|
||||
XEEXPECTZERO(result);
|
||||
|
||||
size_t buffer_length = file_info.file_length;
|
||||
buffer = (uint8_t*)xe_malloc(buffer_length);
|
||||
|
||||
// Open file for reading.
|
||||
result = fs_entry->Open(kernel_state(), kXEFileModeRead, false, &file);
|
||||
XEEXPECTZERO(result);
|
||||
|
||||
// Read entire file into memory.
|
||||
// Ugh.
|
||||
size_t bytes_read = 0;
|
||||
result = file->Read(buffer, buffer_length, 0, &bytes_read);
|
||||
XEEXPECTZERO(result);
|
||||
|
||||
// Load the module.
|
||||
result = LoadFromMemory(buffer, bytes_read);
|
||||
}
|
||||
|
||||
// Load the module.
|
||||
X_STATUS return_code = LoadFromMemory(mmap->address(), mmap->length());
|
||||
|
||||
// Unmap memory and cleanup.
|
||||
delete mmap;
|
||||
XECLEANUP:
|
||||
if (buffer) {
|
||||
xe_free(buffer);
|
||||
}
|
||||
if (file) {
|
||||
file->Release();
|
||||
}
|
||||
delete fs_entry;
|
||||
|
||||
return return_code;
|
||||
return result;
|
||||
}
|
||||
|
||||
X_STATUS XUserModule::LoadFromMemory(const void* addr, const size_t length) {
|
||||
|
|
|
@ -53,12 +53,8 @@ int xenia_run(int argc, xechar_t** argv) {
|
|||
xe_path_get_absolute(path, abs_path, XECOUNT(abs_path));
|
||||
|
||||
// Grab file extension.
|
||||
// May be NULL if an STFS file.
|
||||
const xechar_t* dot = xestrrchr(abs_path, '.');
|
||||
if (!dot) {
|
||||
XELOGE("Invalid input path; no extension found");
|
||||
XEFATAL("Pass a valid file path to launch.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the emulator.
|
||||
emulator = new Emulator(XT(""));
|
||||
|
@ -72,7 +68,10 @@ int xenia_run(int argc, xechar_t** argv) {
|
|||
// Launch based on file type.
|
||||
// This is a silly guess based on file extension.
|
||||
// NOTE: the runtime launch routine will wait until the module exits.
|
||||
if (xestrcmp(dot, XT(".xex")) == 0) {
|
||||
if (!dot) {
|
||||
// Likely an STFS container.
|
||||
result = emulator->LaunchSTFSTitle(abs_path);
|
||||
} else if (xestrcmp(dot, XT(".xex")) == 0) {
|
||||
// Treat as a naked xex file.
|
||||
result = emulator->LaunchXexFile(abs_path);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue