Merge 2 remote-tracking branch 'emoose/stfs-packages, emoose/title-updates' into canary-cleanup
Originally merged by @0x8080 Co-Authored-By: 0x8080 <0x8080@users.noreply.github.com>
This commit is contained in:
parent
4e270843d7
commit
9f9d8ba92d
|
@ -19,6 +19,7 @@
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/kernel/xfile.h"
|
#include "xenia/kernel/xfile.h"
|
||||||
#include "xenia/kernel/xthread.h"
|
#include "xenia/kernel/xthread.h"
|
||||||
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
|
|
||||||
DEFINE_bool(xex_apply_patches, true, "Apply XEX patches.", "Kernel");
|
DEFINE_bool(xex_apply_patches, true, "Apply XEX patches.", "Kernel");
|
||||||
|
|
||||||
|
@ -51,9 +52,11 @@ uint32_t UserModule::title_id() const {
|
||||||
X_STATUS UserModule::LoadFromFile(std::string path) {
|
X_STATUS UserModule::LoadFromFile(std::string path) {
|
||||||
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
X_STATUS result = X_STATUS_UNSUCCESSFUL;
|
||||||
|
|
||||||
|
auto file_system = kernel_state()->file_system();
|
||||||
|
|
||||||
// Resolve the file to open.
|
// Resolve the file to open.
|
||||||
// TODO(benvanik): make this code shared?
|
// TODO(benvanik): make this code shared?
|
||||||
auto fs_entry = kernel_state()->file_system()->ResolvePath(path);
|
auto fs_entry = file_system->ResolvePath(path);
|
||||||
if (!fs_entry) {
|
if (!fs_entry) {
|
||||||
XELOGE("File not found: %s", path.c_str());
|
XELOGE("File not found: %s", path.c_str());
|
||||||
return X_STATUS_NO_SUCH_FILE;
|
return X_STATUS_NO_SUCH_FILE;
|
||||||
|
@ -105,7 +108,48 @@ X_STATUS UserModule::LoadFromFile(std::string path) {
|
||||||
if (cvars::xex_apply_patches) {
|
if (cvars::xex_apply_patches) {
|
||||||
// Search for xexp patch file
|
// Search for xexp patch file
|
||||||
auto patch_entry = kernel_state()->file_system()->ResolvePath(path_ + "p");
|
auto patch_entry = kernel_state()->file_system()->ResolvePath(path_ + "p");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto module_path = fs_entry->path();
|
||||||
|
|
||||||
|
auto content_manager = kernel_state()->content_manager();
|
||||||
|
|
||||||
|
if (!file_system->IsSymbolicLink("update:")) {
|
||||||
|
// update:\\ path isn't symlinked, try searching for an update package
|
||||||
|
|
||||||
|
xex2_opt_execution_info* exec_info = 0;
|
||||||
|
xex_module()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &exec_info);
|
||||||
|
|
||||||
|
if (exec_info) {
|
||||||
|
content_manager->SetTitleIdOverride(exec_info->title_id);
|
||||||
|
|
||||||
|
auto update_packages = content_manager->ListContent(
|
||||||
|
0, (uint32_t)vfs::StfsContentType::kInstaller);
|
||||||
|
|
||||||
|
for (auto& update : update_packages) {
|
||||||
|
auto result = content_manager->OpenContent("update", update);
|
||||||
|
|
||||||
|
if (!file_system->ResolvePath("update:\\" + module_path + "p")) {
|
||||||
|
// XEXP/DLLP doesn't exist in this package, lets just close it
|
||||||
|
content_manager->CloseContent("update");
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// XEXP/DLLP found, break out of package loop
|
||||||
|
// TODO: verify XEXP/DLLP works first?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unset content_manager title ID override
|
||||||
|
content_manager->SetTitleIdOverride(0);
|
||||||
|
|
||||||
|
// First try checking update:\ root for patch, otherwise try same path as XEX
|
||||||
|
auto patch_entry = file_system->ResolvePath("update:\\" + module_path + "p");
|
||||||
|
if (!patch_entry) {
|
||||||
|
patch_entry = file_system->ResolvePath(path_ + "p");
|
||||||
|
}
|
||||||
if (patch_entry) {
|
if (patch_entry) {
|
||||||
auto patch_path = patch_entry->absolute_path();
|
auto patch_path = patch_entry->absolute_path();
|
||||||
|
|
||||||
|
@ -126,7 +170,6 @@ X_STATUS UserModule::LoadFromFile(std::string path) {
|
||||||
return X_STATUS_UNSUCCESSFUL;
|
return X_STATUS_UNSUCCESSFUL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return LoadXexContinue();
|
return LoadXexContinue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,49 +14,42 @@
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
#include "xenia/kernel/user_module.h"
|
||||||
|
#include "xenia/kernel/xam/content_package.h"
|
||||||
#include "xenia/kernel/xobject.h"
|
#include "xenia/kernel/xobject.h"
|
||||||
#include "xenia/vfs/devices/host_path_device.h"
|
#include "xenia/vfs/devices/host_path_device.h"
|
||||||
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
static const wchar_t* kThumbnailFileName = L"__thumbnail.png";
|
constexpr const wchar_t* const ContentManager::kStfsHeadersExtension;
|
||||||
|
|
||||||
static const wchar_t* kGameUserContentDirName = L"profile";
|
static const wchar_t* kGameUserContentDirName = L"profile";
|
||||||
|
|
||||||
static int content_device_id_ = 0;
|
|
||||||
|
|
||||||
ContentPackage::ContentPackage(KernelState* kernel_state, std::string root_name,
|
|
||||||
const XCONTENT_DATA& data,
|
|
||||||
std::wstring package_path)
|
|
||||||
: kernel_state_(kernel_state), root_name_(std::move(root_name)) {
|
|
||||||
device_path_ = std::string("\\Device\\Content\\") +
|
|
||||||
std::to_string(++content_device_id_) + "\\";
|
|
||||||
|
|
||||||
auto fs = kernel_state_->file_system();
|
|
||||||
auto device =
|
|
||||||
std::make_unique<vfs::HostPathDevice>(device_path_, package_path, false);
|
|
||||||
device->Initialize();
|
|
||||||
fs->RegisterDevice(std::move(device));
|
|
||||||
fs->RegisterSymbolicLink(root_name_ + ":", device_path_);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentPackage::~ContentPackage() {
|
|
||||||
auto fs = kernel_state_->file_system();
|
|
||||||
fs->UnregisterSymbolicLink(root_name_ + ":");
|
|
||||||
fs->UnregisterDevice(device_path_);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentManager::ContentManager(KernelState* kernel_state,
|
ContentManager::ContentManager(KernelState* kernel_state,
|
||||||
std::wstring root_path)
|
std::wstring root_path)
|
||||||
: kernel_state_(kernel_state), root_path_(std::move(root_path)) {}
|
: kernel_state_(kernel_state), root_path_(std::move(root_path)) {}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
|
|
||||||
|
uint32_t ContentManager::title_id() {
|
||||||
|
if (title_id_override_) {
|
||||||
|
return title_id_override_;
|
||||||
|
}
|
||||||
|
if (!kernel_state_->GetExecutableModule()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return kernel_state_->title_id();
|
||||||
|
}
|
||||||
|
|
||||||
std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
||||||
wchar_t title_id[9] = L"00000000";
|
wchar_t title_id_str[9] = L"00000000";
|
||||||
std::swprintf(title_id, 9, L"%.8X", kernel_state_->title_id());
|
std::swprintf(title_id_str, 9, L"%.8X", title_id());
|
||||||
|
|
||||||
|
wchar_t content_type_str[9] = L"00000000";
|
||||||
|
std::swprintf(content_type_str, 9, L"%.8X", content_type);
|
||||||
|
|
||||||
std::wstring type_name;
|
std::wstring type_name;
|
||||||
switch (content_type) {
|
switch (content_type) {
|
||||||
|
@ -77,14 +70,15 @@ std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
||||||
type_name = L"000D0000";
|
type_name = L"000D0000";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert_unhandled_case(data.content_type);
|
type_name = L"00000000";
|
||||||
return nullptr;
|
//assert_unhandled_case(data.content_type);
|
||||||
|
//return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package root path:
|
// Package root path:
|
||||||
// content_root/title_id/type_name/
|
// content_root/title_id/type_name/
|
||||||
auto package_root =
|
auto package_root = xe::join_paths(
|
||||||
xe::join_paths(root_path_, xe::join_paths(title_id, type_name));
|
root_path_, xe::join_paths(title_id_str, content_type_str));
|
||||||
return package_root + xe::kWPathSeparator;
|
return package_root + xe::kWPathSeparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +88,13 @@ std::wstring ContentManager::ResolvePackagePath(const XCONTENT_DATA& data) {
|
||||||
auto package_root = ResolvePackageRoot(data.content_type);
|
auto package_root = ResolvePackageRoot(data.content_type);
|
||||||
auto package_path =
|
auto package_path =
|
||||||
xe::join_paths(package_root, xe::to_wstring(data.file_name));
|
xe::join_paths(package_root, xe::to_wstring(data.file_name));
|
||||||
|
|
||||||
|
// Add slash to end of path if this is a folder
|
||||||
|
// (or package doesn't exist, meaning we're creating a new folder)
|
||||||
|
if (!xe::filesystem::PathExists(package_path) ||
|
||||||
|
xe::filesystem::IsFolder(package_path)) {
|
||||||
package_path += xe::kPathSeparator;
|
package_path += xe::kPathSeparator;
|
||||||
|
}
|
||||||
return package_path;
|
return package_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,28 +102,58 @@ std::vector<XCONTENT_DATA> ContentManager::ListContent(uint32_t device_id,
|
||||||
uint32_t content_type) {
|
uint32_t content_type) {
|
||||||
std::vector<XCONTENT_DATA> result;
|
std::vector<XCONTENT_DATA> result;
|
||||||
|
|
||||||
|
// StfsHeader is a huge class - alloc on heap instead of stack
|
||||||
|
vfs::StfsHeader* header = new vfs::StfsHeader();
|
||||||
|
|
||||||
// Search path:
|
// Search path:
|
||||||
// content_root/title_id/type_name/*
|
// content_root/title_id/type_name/*
|
||||||
auto package_root = ResolvePackageRoot(content_type);
|
auto package_root = ResolvePackageRoot(content_type);
|
||||||
auto file_infos = xe::filesystem::ListFiles(package_root);
|
auto file_infos = xe::filesystem::ListFiles(package_root);
|
||||||
for (const auto& file_info : file_infos) {
|
for (const auto& file_info : file_infos) {
|
||||||
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
|
|
||||||
// Directories only.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
XCONTENT_DATA content_data;
|
XCONTENT_DATA content_data;
|
||||||
content_data.device_id = device_id;
|
content_data.device_id = device_id;
|
||||||
content_data.content_type = content_type;
|
content_data.content_type = content_type;
|
||||||
content_data.display_name = file_info.name;
|
content_data.display_name = file_info.name;
|
||||||
content_data.file_name = xe::to_string(file_info.name);
|
content_data.file_name = xe::to_string(file_info.name);
|
||||||
|
|
||||||
|
auto headers_path = file_info.path + file_info.name;
|
||||||
|
if (file_info.type == xe::filesystem::FileInfo::Type::kDirectory) {
|
||||||
|
headers_path = headers_path + ContentManager::kStfsHeadersExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xe::filesystem::PathExists(headers_path)) {
|
||||||
|
// File is either package or directory that has .headers file
|
||||||
|
|
||||||
|
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
|
||||||
|
// Not a directory so must be a package, verify size to make sure
|
||||||
|
if (file_info.total_size <= vfs::StfsHeader::kHeaderLength) {
|
||||||
|
continue; // Invalid package (maybe .headers.bin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto map = MappedMemory::Open(headers_path, MappedMemory::Mode::kRead, 0,
|
||||||
|
vfs::StfsHeader::kHeaderLength);
|
||||||
|
if (map) {
|
||||||
|
if (header->Read(map->data())) {
|
||||||
|
content_data.content_type =
|
||||||
|
static_cast<uint32_t>(header->content_type);
|
||||||
|
content_data.display_name = header->display_names;
|
||||||
|
// TODO: select localized display name
|
||||||
|
// some games may expect different ones depending on language setting.
|
||||||
|
}
|
||||||
|
map->Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.emplace_back(std::move(content_data));
|
result.emplace_back(std::move(content_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete header;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
ContentPackage* ContentManager::ResolvePackage(const XCONTENT_DATA& data) {
|
||||||
std::string root_name, const XCONTENT_DATA& data) {
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(data);
|
||||||
if (!xe::filesystem::PathExists(package_path)) {
|
if (!xe::filesystem::PathExists(package_path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -131,9 +161,25 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
||||||
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
auto package = std::make_unique<ContentPackage>(kernel_state_, root_name,
|
for (auto package : open_packages_) {
|
||||||
data, package_path);
|
if (package->package_path() == package_path) {
|
||||||
return package;
|
return package;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ContentPackage> package;
|
||||||
|
|
||||||
|
// Open as FolderContentPackage if the package is a folder or doesn't exist
|
||||||
|
if (xe::filesystem::IsFolder(package_path) ||
|
||||||
|
!xe::filesystem::PathExists(package_path)) {
|
||||||
|
package = std::make_unique<FolderContentPackage>(kernel_state_, data,
|
||||||
|
package_path);
|
||||||
|
} else {
|
||||||
|
package =
|
||||||
|
std::make_unique<StfsContentPackage>(kernel_state_, data, package_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return package.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentManager::ContentExists(const XCONTENT_DATA& data) {
|
bool ContentManager::ContentExists(const XCONTENT_DATA& data) {
|
||||||
|
@ -145,25 +191,32 @@ X_RESULT ContentManager::CreateContent(std::string root_name,
|
||||||
const XCONTENT_DATA& data) {
|
const XCONTENT_DATA& data) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
if (open_packages_.count(root_name)) {
|
|
||||||
// Already content open with this root name.
|
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(data);
|
||||||
if (xe::filesystem::PathExists(package_path)) {
|
if (xe::filesystem::PathExists(package_path)) {
|
||||||
// Exists, must not!
|
// Exists, must not!
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto package : open_packages_) {
|
||||||
|
if (package->package_path() == package_path) {
|
||||||
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!xe::filesystem::CreateFolder(package_path)) {
|
if (!xe::filesystem::CreateFolder(package_path)) {
|
||||||
return X_ERROR_ACCESS_DENIED;
|
return X_ERROR_ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package = ResolvePackage(root_name, data);
|
auto package = ResolvePackage(data);
|
||||||
assert_not_null(package);
|
if (!package) {
|
||||||
|
return X_ERROR_FUNCTION_FAILED; // Failed to create directory?
|
||||||
|
}
|
||||||
|
|
||||||
open_packages_.insert({root_name, package.release()});
|
if (!package->Mount(root_name)) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
open_packages_.push_back(package);
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -172,22 +225,16 @@ X_RESULT ContentManager::OpenContent(std::string root_name,
|
||||||
const XCONTENT_DATA& data) {
|
const XCONTENT_DATA& data) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
if (open_packages_.count(root_name)) {
|
auto package = ResolvePackage(data);
|
||||||
// Already content open with this root name.
|
if (!package) {
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
if (!xe::filesystem::PathExists(package_path)) {
|
|
||||||
// Does not exist, must be created.
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open package.
|
if (!package->Mount(root_name)) {
|
||||||
auto package = ResolvePackage(root_name, data);
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
assert_not_null(package);
|
}
|
||||||
|
|
||||||
open_packages_.insert({root_name, package.release()});
|
open_packages_.push_back(package);
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -195,63 +242,65 @@ X_RESULT ContentManager::OpenContent(std::string root_name,
|
||||||
X_RESULT ContentManager::CloseContent(std::string root_name) {
|
X_RESULT ContentManager::CloseContent(std::string root_name) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
auto it = open_packages_.find(root_name);
|
for (auto it = open_packages_.begin(); it != open_packages_.end(); ++it) {
|
||||||
if (it == open_packages_.end()) {
|
auto& root_names = (*it)->root_names();
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
auto root = std::find(root_names.begin(), root_names.end(), root_name);
|
||||||
|
if (root != root_names.end()) {
|
||||||
|
if ((*it)->Unmount(root_name)) {
|
||||||
|
delete *it;
|
||||||
|
open_packages_.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package = it->second;
|
|
||||||
open_packages_.erase(it);
|
|
||||||
delete package;
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data,
|
X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data,
|
||||||
std::vector<uint8_t>* buffer) {
|
std::vector<uint8_t>* buffer) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
auto thumb_path = xe::join_paths(package_path, kThumbnailFileName);
|
auto package = ResolvePackage(data);
|
||||||
if (xe::filesystem::PathExists(thumb_path)) {
|
if (!package) {
|
||||||
auto file = xe::filesystem::OpenFile(thumb_path, "rb");
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
size_t file_len = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
buffer->resize(file_len);
|
|
||||||
fread(const_cast<uint8_t*>(buffer->data()), 1, buffer->size(), file);
|
|
||||||
fclose(file);
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
} else {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return package->GetThumbnail(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data,
|
X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data,
|
||||||
std::vector<uint8_t> buffer) {
|
std::vector<uint8_t> buffer) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto package_path = ResolvePackagePath(data);
|
|
||||||
xe::filesystem::CreateFolder(package_path);
|
auto package = ResolvePackage(data);
|
||||||
if (xe::filesystem::PathExists(package_path)) {
|
if (!package) {
|
||||||
auto thumb_path = xe::join_paths(package_path, kThumbnailFileName);
|
|
||||||
auto file = xe::filesystem::OpenFile(thumb_path, "wb");
|
|
||||||
fwrite(buffer.data(), 1, buffer.size(), file);
|
|
||||||
fclose(file);
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
} else {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return package->SetThumbnail(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) {
|
X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package = ResolvePackage(data);
|
||||||
if (xe::filesystem::PathExists(package_path)) {
|
if (!package) {
|
||||||
xe::filesystem::DeleteFolder(package_path);
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
} else {
|
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto result = package->Delete();
|
||||||
|
if (XSUCCEEDED(result)) {
|
||||||
|
auto it = std::find(open_packages_.begin(), open_packages_.end(), package);
|
||||||
|
if (it != open_packages_.end()) {
|
||||||
|
open_packages_.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete package;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring ContentManager::ResolveGameUserContentPath() {
|
std::wstring ContentManager::ResolveGameUserContentPath() {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/mutex.h"
|
#include "xenia/base/mutex.h"
|
||||||
|
#include "xenia/kernel/xam/content_package.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -52,28 +53,18 @@ struct XCONTENT_DATA {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ContentPackage {
|
|
||||||
public:
|
|
||||||
ContentPackage(KernelState* kernel_state, std::string root_name,
|
|
||||||
const XCONTENT_DATA& data, std::wstring package_path);
|
|
||||||
~ContentPackage();
|
|
||||||
|
|
||||||
private:
|
|
||||||
KernelState* kernel_state_;
|
|
||||||
std::string root_name_;
|
|
||||||
std::string device_path_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ContentManager {
|
class ContentManager {
|
||||||
public:
|
public:
|
||||||
|
// Extension to append to folder path when searching for STFS headers
|
||||||
|
static constexpr const wchar_t* const kStfsHeadersExtension = L".headers.bin";
|
||||||
|
|
||||||
ContentManager(KernelState* kernel_state, std::wstring root_path);
|
ContentManager(KernelState* kernel_state, std::wstring root_path);
|
||||||
~ContentManager();
|
~ContentManager();
|
||||||
|
|
||||||
std::vector<XCONTENT_DATA> ListContent(uint32_t device_id,
|
std::vector<XCONTENT_DATA> ListContent(uint32_t device_id,
|
||||||
uint32_t content_type);
|
uint32_t content_type);
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ResolvePackage(std::string root_name,
|
ContentPackage* ResolvePackage(const XCONTENT_DATA& data);
|
||||||
const XCONTENT_DATA& data);
|
|
||||||
|
|
||||||
bool ContentExists(const XCONTENT_DATA& data);
|
bool ContentExists(const XCONTENT_DATA& data);
|
||||||
X_RESULT CreateContent(std::string root_name, const XCONTENT_DATA& data);
|
X_RESULT CreateContent(std::string root_name, const XCONTENT_DATA& data);
|
||||||
|
@ -86,7 +77,11 @@ class ContentManager {
|
||||||
X_RESULT DeleteContent(const XCONTENT_DATA& data);
|
X_RESULT DeleteContent(const XCONTENT_DATA& data);
|
||||||
std::wstring ResolveGameUserContentPath();
|
std::wstring ResolveGameUserContentPath();
|
||||||
|
|
||||||
|
void SetTitleIdOverride(uint32_t title_id) { title_id_override_ = title_id; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint32_t title_id();
|
||||||
|
|
||||||
std::wstring ResolvePackageRoot(uint32_t content_type);
|
std::wstring ResolvePackageRoot(uint32_t content_type);
|
||||||
std::wstring ResolvePackagePath(const XCONTENT_DATA& data);
|
std::wstring ResolvePackagePath(const XCONTENT_DATA& data);
|
||||||
|
|
||||||
|
@ -95,7 +90,10 @@ class ContentManager {
|
||||||
|
|
||||||
// TODO(benvanik): remove use of global lock, it's bad here!
|
// TODO(benvanik): remove use of global lock, it's bad here!
|
||||||
xe::global_critical_region global_critical_region_;
|
xe::global_critical_region global_critical_region_;
|
||||||
std::unordered_map<std::string, ContentPackage*> open_packages_;
|
std::vector<ContentPackage*> open_packages_;
|
||||||
|
|
||||||
|
uint32_t title_id_override_ =
|
||||||
|
0; // can be used for games/apps that request content for other IDs
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/kernel/xam/content_package.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/base/filesystem.h"
|
||||||
|
#include "xenia/base/string.h"
|
||||||
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
#include "xenia/kernel/xobject.h"
|
||||||
|
#include "xenia/vfs/devices/host_path_device.h"
|
||||||
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xam {
|
||||||
|
|
||||||
|
static const wchar_t* kThumbnailFileName = L"__thumbnail.png";
|
||||||
|
|
||||||
|
static int content_device_id_ = 0;
|
||||||
|
|
||||||
|
ContentPackage::ContentPackage(KernelState* kernel_state,
|
||||||
|
const XCONTENT_DATA& data,
|
||||||
|
std::wstring package_path)
|
||||||
|
: kernel_state_(kernel_state), package_path_(std::move(package_path)) {
|
||||||
|
device_path_ = std::string("\\Device\\Content\\") +
|
||||||
|
std::to_string(++content_device_id_) + "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentPackage::~ContentPackage() { Unmount(); }
|
||||||
|
|
||||||
|
bool ContentPackage::Mount(std::string root_name,
|
||||||
|
std::unique_ptr<vfs::Device> device) {
|
||||||
|
auto fs = kernel_state_->file_system();
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
if (!fs->RegisterDevice(std::move(device))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs->RegisterSymbolicLink(root_name + ":", device_path_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
root_names_.push_back(root_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentPackage::Unmount() {
|
||||||
|
auto fs = kernel_state_->file_system();
|
||||||
|
for (auto root_name : root_names_) {
|
||||||
|
fs->UnregisterSymbolicLink(root_name + ":");
|
||||||
|
}
|
||||||
|
root_names_.clear();
|
||||||
|
fs->UnregisterDevice(device_path_);
|
||||||
|
device_inited_ = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentPackage::Unmount(std::string root_name) {
|
||||||
|
auto itr = std::find(root_names_.begin(), root_names_.end(), root_name);
|
||||||
|
if (itr != root_names_.end()) {
|
||||||
|
root_names_.erase(itr);
|
||||||
|
kernel_state_->file_system()->UnregisterSymbolicLink(root_name + ":");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a complete device unmount if no root names are left
|
||||||
|
if (!root_names_.size()) {
|
||||||
|
return Unmount();
|
||||||
|
}
|
||||||
|
return false; // still mounted under a different root_name, so return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FolderContentPackage::Mount(std::string root_name) {
|
||||||
|
std::unique_ptr<vfs::HostPathDevice> device = nullptr;
|
||||||
|
|
||||||
|
if (!device_inited_) {
|
||||||
|
device = std::make_unique<vfs::HostPathDevice>(device_path_, package_path_,
|
||||||
|
false);
|
||||||
|
if (!device->Initialize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
device_inited_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContentPackage::Mount(root_name, std::move(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT FolderContentPackage::GetThumbnail(std::vector<uint8_t>* buffer) {
|
||||||
|
// Grab thumb from kThumbnailFileName if it exists, otherwise try STFS headers
|
||||||
|
auto thumb_path = xe::join_paths(package_path_, kThumbnailFileName);
|
||||||
|
if (xe::filesystem::PathExists(thumb_path)) {
|
||||||
|
auto file = xe::filesystem::OpenFile(thumb_path, "rb");
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
size_t file_len = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
buffer->resize(file_len);
|
||||||
|
fread(const_cast<uint8_t*>(buffer->data()), 1, buffer->size(), file);
|
||||||
|
fclose(file);
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
auto result = X_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
// Try reading thumbnail from kStfsHeadersExtension file
|
||||||
|
auto headers_path = package_path_ + ContentManager::kStfsHeadersExtension;
|
||||||
|
if (xe::filesystem::PathExists(headers_path)) {
|
||||||
|
vfs::StfsHeader* header =
|
||||||
|
new vfs::StfsHeader(); // huge class, alloc on heap
|
||||||
|
auto map = MappedMemory::Open(headers_path, MappedMemory::Mode::kRead, 0,
|
||||||
|
vfs::StfsHeader::kHeaderLength);
|
||||||
|
if (map) {
|
||||||
|
if (header->Read(map->data())) {
|
||||||
|
buffer->resize(header->thumbnail_image_size);
|
||||||
|
memcpy(buffer->data(), header->thumbnail_image,
|
||||||
|
header->thumbnail_image_size);
|
||||||
|
result = X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete header;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT FolderContentPackage::SetThumbnail(std::vector<uint8_t> buffer) {
|
||||||
|
xe::filesystem::CreateFolder(package_path_);
|
||||||
|
if (xe::filesystem::PathExists(package_path_)) {
|
||||||
|
auto thumb_path = xe::join_paths(package_path_, kThumbnailFileName);
|
||||||
|
auto file = xe::filesystem::OpenFile(thumb_path, "wb");
|
||||||
|
fwrite(buffer.data(), 1, buffer.size(), file);
|
||||||
|
fclose(file);
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT FolderContentPackage::Delete() {
|
||||||
|
// Make sure package isn't in use
|
||||||
|
Unmount();
|
||||||
|
|
||||||
|
if (xe::filesystem::PathExists(package_path_)) {
|
||||||
|
xe::filesystem::DeleteFolder(package_path_);
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StfsContentPackage::Mount(std::string root_name) {
|
||||||
|
std::unique_ptr<vfs::StfsContainerDevice> device = nullptr;
|
||||||
|
|
||||||
|
if (!device_inited_) {
|
||||||
|
device =
|
||||||
|
std::make_unique<vfs::StfsContainerDevice>(device_path_, package_path_);
|
||||||
|
if (!device->Initialize()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_ = device->header();
|
||||||
|
device_inited_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContentPackage::Mount(root_name, std::move(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT StfsContentPackage::GetThumbnail(std::vector<uint8_t>* buffer) {
|
||||||
|
if (!device_inited_) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
buffer->resize(header_.thumbnail_image_size);
|
||||||
|
memcpy(buffer->data(), header_.thumbnail_image, header_.thumbnail_image_size);
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT StfsContentPackage::SetThumbnail(std::vector<uint8_t> buffer) {
|
||||||
|
return X_ERROR_FUNCTION_FAILED; // can't write to STFS headers right now
|
||||||
|
}
|
||||||
|
|
||||||
|
X_RESULT StfsContentPackage::Delete() {
|
||||||
|
// Make sure package isn't in use
|
||||||
|
Unmount();
|
||||||
|
|
||||||
|
if (xe::filesystem::PathExists(package_path_)) {
|
||||||
|
return xe::filesystem::DeleteFile(package_path_) ? X_ERROR_SUCCESS
|
||||||
|
: X_ERROR_FUNCTION_FAILED;
|
||||||
|
}
|
||||||
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xam
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_KERNEL_XAM_CONTENT_PACKAGE_H_
|
||||||
|
#define XENIA_KERNEL_XAM_CONTENT_PACKAGE_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/memory.h"
|
||||||
|
#include "xenia/base/mutex.h"
|
||||||
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
namespace xam {
|
||||||
|
|
||||||
|
struct XCONTENT_DATA;
|
||||||
|
|
||||||
|
class ContentPackage {
|
||||||
|
public:
|
||||||
|
ContentPackage(KernelState* kernel_state, const XCONTENT_DATA& data,
|
||||||
|
std::wstring package_path);
|
||||||
|
virtual ~ContentPackage();
|
||||||
|
|
||||||
|
virtual bool Mount(std::string root_name) = 0;
|
||||||
|
virtual X_RESULT GetThumbnail(std::vector<uint8_t>* buffer) = 0;
|
||||||
|
virtual X_RESULT SetThumbnail(std::vector<uint8_t> buffer) = 0;
|
||||||
|
virtual X_RESULT Delete() = 0;
|
||||||
|
|
||||||
|
bool Unmount(); // Unmounts device & all root names - returns true if device
|
||||||
|
// is unmounted
|
||||||
|
bool Unmount(
|
||||||
|
std::string root_name); // Unmounts a single root name, if no root names
|
||||||
|
// are left mounted the device will be unmounted
|
||||||
|
// - returns true if device is unmounted
|
||||||
|
|
||||||
|
std::wstring package_path() { return package_path_; }
|
||||||
|
const std::vector<std::string>& root_names() { return root_names_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool Mount(std::string root_name, std::unique_ptr<vfs::Device> device);
|
||||||
|
|
||||||
|
bool device_inited_ = false;
|
||||||
|
KernelState* kernel_state_;
|
||||||
|
std::string device_path_;
|
||||||
|
std::wstring package_path_;
|
||||||
|
std::vector<std::string> root_names_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StfsContentPackage : public ContentPackage {
|
||||||
|
public:
|
||||||
|
StfsContentPackage(KernelState* kernel_state, const XCONTENT_DATA& data,
|
||||||
|
std::wstring package_path)
|
||||||
|
: ContentPackage(kernel_state, data, package_path) {}
|
||||||
|
|
||||||
|
bool Mount(std::string root_name);
|
||||||
|
X_RESULT GetThumbnail(std::vector<uint8_t>* buffer);
|
||||||
|
X_RESULT SetThumbnail(std::vector<uint8_t> buffer);
|
||||||
|
X_RESULT Delete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
vfs::StfsHeader header_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FolderContentPackage : public ContentPackage {
|
||||||
|
public:
|
||||||
|
FolderContentPackage(KernelState* kernel_state, const XCONTENT_DATA& data,
|
||||||
|
std::wstring package_path)
|
||||||
|
: ContentPackage(kernel_state, data, package_path) {}
|
||||||
|
|
||||||
|
bool Mount(std::string root_name);
|
||||||
|
X_RESULT GetThumbnail(std::vector<uint8_t>* buffer);
|
||||||
|
X_RESULT SetThumbnail(std::vector<uint8_t> buffer);
|
||||||
|
X_RESULT Delete();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace xam
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_XAM_CONTENT_PACKAGE_H_
|
|
@ -117,6 +117,8 @@ struct SvodVolumeDescriptor {
|
||||||
|
|
||||||
class StfsHeader {
|
class StfsHeader {
|
||||||
public:
|
public:
|
||||||
|
static const uint32_t kHeaderLength = 0xA000;
|
||||||
|
|
||||||
bool Read(const uint8_t* p);
|
bool Read(const uint8_t* p);
|
||||||
|
|
||||||
uint8_t license_entries[0x100];
|
uint8_t license_entries[0x100];
|
||||||
|
@ -181,6 +183,8 @@ class StfsContainerDevice : public Device {
|
||||||
uint32_t sectors_per_allocation_unit() const override { return 1; }
|
uint32_t sectors_per_allocation_unit() const override { return 1; }
|
||||||
uint32_t bytes_per_sector() const override { return 4 * 1024; }
|
uint32_t bytes_per_sector() const override { return 4 * 1024; }
|
||||||
|
|
||||||
|
StfsHeader& header() { return header_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Error {
|
enum class Error {
|
||||||
kSuccess = 0,
|
kSuccess = 0,
|
||||||
|
|
|
@ -25,6 +25,26 @@ VirtualFileSystem::~VirtualFileSystem() {
|
||||||
devices_.clear();
|
devices_.clear();
|
||||||
symlinks_.clear();
|
symlinks_.clear();
|
||||||
}
|
}
|
||||||
|
Entry* VirtualFileSystem::ResolveDevice(const std::string& devicepath) {
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
|
// Resolve relative paths
|
||||||
|
std::string normalized_path(xe::filesystem::CanonicalizePath(devicepath));
|
||||||
|
|
||||||
|
// Find the device.
|
||||||
|
auto it =
|
||||||
|
std::find_if(devices_.cbegin(), devices_.cend(), [&](const auto& d) {
|
||||||
|
return xe::find_first_of_case(normalized_path, d->mount_path()) == 0;
|
||||||
|
});
|
||||||
|
if (it == devices_.cend()) {
|
||||||
|
XELOGE("ResolveDevice(%s) device not initialized", devicepath.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& device = *it;
|
||||||
|
auto relative_path = normalized_path.substr(device->mount_path().size());
|
||||||
|
return device->ResolvePath(relative_path);
|
||||||
|
}
|
||||||
|
|
||||||
bool VirtualFileSystem::RegisterDevice(std::unique_ptr<Device> device) {
|
bool VirtualFileSystem::RegisterDevice(std::unique_ptr<Device> device) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
@ -65,7 +85,6 @@ bool VirtualFileSystem::UnregisterSymbolicLink(const std::string& path) {
|
||||||
symlinks_.erase(it);
|
symlinks_.erase(it);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VirtualFileSystem::IsSymbolicLink(const std::string& path) {
|
bool VirtualFileSystem::IsSymbolicLink(const std::string& path) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto it = symlinks_.find(path);
|
auto it = symlinks_.find(path);
|
||||||
|
|
|
@ -27,13 +27,14 @@ class VirtualFileSystem {
|
||||||
public:
|
public:
|
||||||
VirtualFileSystem();
|
VirtualFileSystem();
|
||||||
~VirtualFileSystem();
|
~VirtualFileSystem();
|
||||||
|
Entry* ResolveDevice(const std::string& path);
|
||||||
|
|
||||||
bool RegisterDevice(std::unique_ptr<Device> device);
|
bool RegisterDevice(std::unique_ptr<Device> device);
|
||||||
bool UnregisterDevice(const std::string& path);
|
bool UnregisterDevice(const std::string& path);
|
||||||
|
|
||||||
bool RegisterSymbolicLink(const std::string& path, const std::string& target);
|
bool RegisterSymbolicLink(const std::string& path, const std::string& target);
|
||||||
bool UnregisterSymbolicLink(const std::string& path);
|
bool UnregisterSymbolicLink(const std::string& path);
|
||||||
bool VirtualFileSystem::IsSymbolicLink(const std::string& path);
|
bool IsSymbolicLink(const std::string& path);
|
||||||
bool FindSymbolicLink(const std::string& path, std::string& target);
|
bool FindSymbolicLink(const std::string& path, std::string& target);
|
||||||
|
|
||||||
Entry* ResolvePath(const std::string& path);
|
Entry* ResolvePath(const std::string& path);
|
||||||
|
|
Loading…
Reference in New Issue