STFS loading... xex's load, but files seem broken. Ugh.

This commit is contained in:
Ben Vanik 2014-01-18 22:23:26 -08:00
parent 69320ed94b
commit 6b633e4e28
24 changed files with 1049 additions and 29 deletions

View File

@ -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)))

View File

@ -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");
}

View File

@ -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];

View File

@ -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);

View File

@ -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;
}

View File

@ -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(

View File

@ -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*)&current->file_name[0]) + file_name_length);

View File

@ -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);

View File

@ -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(

View File

@ -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',
],
}

View File

@ -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);
}

View File

@ -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_

View File

@ -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;
}

View File

@ -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_

View File

@ -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;
}

View File

@ -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_

View File

@ -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,

View File

@ -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),

View File

@ -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);

View File

@ -9,6 +9,8 @@
'filesystem.h',
'gdfx.cc',
'gdfx.h',
'stfs.cc',
'stfs.h',
],
'includes': [

342
src/xenia/kernel/fs/stfs.cc Normal file
View File

@ -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 };
}

208
src/xenia/kernel/fs/stfs.h Normal file
View File

@ -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_

View File

@ -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) {

View File

@ -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 {