diff --git a/.gitignore b/.gitignore index 5f95bcf29..cd9b16c99 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,7 @@ build-test/ .vagrant attic/ +content/ third_party/binutils/binutils-2.24.tar.gz third_party/binutils/bin/ third_party/binutils/powerpc-none-elf/ diff --git a/src/poly/fs.h b/src/poly/fs.h new file mode 100644 index 000000000..f6bd91dc1 --- /dev/null +++ b/src/poly/fs.h @@ -0,0 +1,41 @@ +/** + ****************************************************************************** + * 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 POLY_FS_H_ +#define POLY_FS_H_ + +#include + +#include "poly/config.h" +#include "poly/string.h" + +namespace poly { +namespace fs { + +bool PathExists(const std::wstring& path); + +bool CreateFolder(const std::wstring& path); + +bool DeleteFolder(const std::wstring& path); + +struct FileInfo { + enum class Type { + kFile, + kDirectory, + }; + Type type; + std::wstring name; + size_t total_size; +}; +std::vector ListFiles(const std::wstring& path); + +} // namespace fs +} // namespace poly + +#endif // POLY_FS_H_ diff --git a/src/poly/fs_win.cc b/src/poly/fs_win.cc new file mode 100644 index 000000000..2a414f2b5 --- /dev/null +++ b/src/poly/fs_win.cc @@ -0,0 +1,70 @@ +/** + ****************************************************************************** + * 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 "poly/fs.h" + +#include + +#include + +#include "poly/platform.h" + +namespace poly { +namespace fs { + +bool PathExists(const std::wstring& path) { + DWORD attrib = GetFileAttributes(path.c_str()); + return attrib != INVALID_FILE_ATTRIBUTES; +} + +bool CreateFolder(const std::wstring& path) { + wchar_t folder[MAX_PATH] = {0}; + auto end = std::wcschr(path.c_str(), L'\\'); + while (end) { + wcsncpy(folder, path.c_str(), end - path.c_str() + 1); + CreateDirectory(folder, NULL); + end = wcschr(++end, L'\\'); + } + return PathExists(path); +} + +bool DeleteFolder(const std::wstring& path) { + auto double_null_path = path + L"\0"; + SHFILEOPSTRUCT op = {0}; + op.wFunc = FO_DELETE; + op.pFrom = double_null_path.c_str(); + op.fFlags = FOF_NO_UI; + return SHFileOperation(&op) == 0; +} + +std::vector ListFiles(const std::wstring& path) { + std::vector result; + + WIN32_FIND_DATA ffd; + HANDLE handle = FindFirstFile(path.c_str(), &ffd); + if (handle == INVALID_HANDLE_VALUE) { + return result; + } + do { + FileInfo info; + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + info.type = FileInfo::Type::kDirectory; + } else { + info.type = FileInfo::Type::kFile; + info.total_size = (ffd.nFileSizeHigh * (MAXDWORD + 1)) + ffd.nFileSizeLow; + } + result.push_back(info); + } while (FindNextFile(handle, &ffd) != 0); + FindClose(handle); + + return result; +} + +} // namespace fs +} // namespace poly diff --git a/src/poly/sources.gypi b/src/poly/sources.gypi index 876712daf..2d8f6f320 100644 --- a/src/poly/sources.gypi +++ b/src/poly/sources.gypi @@ -8,6 +8,7 @@ 'delegate.h', 'config.h', 'cxx_compat.h', + 'fs.h', 'logging.cc', 'logging.h', 'main.h', @@ -45,6 +46,7 @@ ['OS == "win"', { 'sources': [ 'debugging_win.cc', + 'fs_win.cc', 'main_win.cc', 'mapped_memory_win.cc', 'threading_win.cc', diff --git a/src/xenia/kernel/content_manager.cc b/src/xenia/kernel/content_manager.cc new file mode 100644 index 000000000..43d0f1f1b --- /dev/null +++ b/src/xenia/kernel/content_manager.cc @@ -0,0 +1,214 @@ +/** + ****************************************************************************** + * 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/content_manager.h" + +#include + +#include "poly/fs.h" +#include "xenia/kernel/xobject.h" + +namespace xe { +namespace kernel { + +static const wchar_t* kThumbnailFileName = L"__thumbnail.png"; + +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_) + "\\"; + kernel_state_->file_system()->RegisterHostPathDevice(device_path_, + package_path, + false); + kernel_state_->file_system()->CreateSymbolicLink(root_name_ + ":", + device_path_); +} + +ContentPackage::~ContentPackage() { + kernel_state_->file_system()->DeleteSymbolicLink(root_name_ + ":"); + // TODO(benvanik): unregister device. +} + +ContentManager::ContentManager(KernelState* kernel_state, + std::wstring root_path) + : kernel_state_(kernel_state), root_path_(std::move(root_path)) {} + +ContentManager::~ContentManager() = default; + +std::wstring ContentManager::ResolvePackagePath(const XCONTENT_DATA& data) { + wchar_t title_id[9] = L"00000000"; + std::swprintf(title_id, 9, L"%.8X", kernel_state_->title_id()); + + std::wstring type_name; + switch (data.content_type) { + case 1: + // Save games. + type_name = L"00000001"; + break; + case 2: + // DLC from the marketplace. + type_name = L"00000002"; + break; + case 3: + // Publisher content? + type_name = L"00000003"; + break; + default: + assert_unhandled_case(data.content_type); + return nullptr; + } + + // Content path: + // content_root/title_id/type_name/data_file_name/ + std::wstring package_path = poly::join_paths( + root_path_, + poly::join_paths( + title_id, + poly::join_paths(type_name, poly::to_wstring(data.file_name)))); + package_path += poly::path_separator; + + return package_path; +} + +std::unique_ptr ContentManager::ResolvePackage( + std::string root_name, const XCONTENT_DATA& data) { + auto package_path = ResolvePackagePath(data); + if (!poly::fs::PathExists(package_path)) { + return nullptr; + } + + std::lock_guard lock(content_mutex_); + + auto package = std::make_unique(kernel_state_, root_name, + data, package_path); + return package; +} + +bool ContentManager::ContentExists(const XCONTENT_DATA& data) { + auto path = ResolvePackagePath(data); + return poly::fs::PathExists(path); +} + +X_RESULT ContentManager::CreateContent(std::string root_name, + const XCONTENT_DATA& data) { + std::lock_guard lock(content_mutex_); + + if (open_packages_.count(root_name)) { + // Already content open with this root name. + return X_ERROR_INVALID_NAME; + } + + auto package_path = ResolvePackagePath(data); + if (poly::fs::PathExists(package_path)) { + // Exists, must not! + return X_ERROR_ALREADY_EXISTS; + } + + if (!poly::fs::CreateFolder(package_path)) { + return X_ERROR_ACCESS_DENIED; + } + + auto package = ResolvePackage(root_name, data); + assert_not_null(package); + + open_packages_.insert({root_name, package.release()}); + + return X_ERROR_SUCCESS; +} + +X_RESULT ContentManager::OpenContent(std::string root_name, + const XCONTENT_DATA& data) { + std::lock_guard lock(content_mutex_); + + if (open_packages_.count(root_name)) { + // Already content open with this root name. + return X_ERROR_INVALID_NAME; + } + + auto package_path = ResolvePackagePath(data); + if (!poly::fs::PathExists(package_path)) { + // Does not exist, must be created. + return X_ERROR_FILE_NOT_FOUND; + } + + // Open package. + auto package = ResolvePackage(root_name, data); + assert_not_null(package); + + open_packages_.insert({root_name, package.release()}); + + return X_ERROR_SUCCESS; +} + +X_RESULT ContentManager::CloseContent(std::string root_name) { + std::lock_guard lock(content_mutex_); + + auto it = open_packages_.find(root_name); + if (it == open_packages_.end()) { + return X_ERROR_FILE_NOT_FOUND; + } + + auto package = it->second; + open_packages_.erase(it); + delete package; + + return X_ERROR_SUCCESS; +} + +X_RESULT ContentManager::GetContentThumbnail(const XCONTENT_DATA& data, + std::vector* buffer) { + std::lock_guard lock(content_mutex_); + auto package_path = ResolvePackagePath(data); + auto thumb_path = poly::join_paths(package_path, kThumbnailFileName); + if (poly::fs::PathExists(thumb_path)) { + auto file = _wfopen(thumb_path.c_str(), L"rb"); + fseek(file, 0, SEEK_END); + size_t file_len = ftell(file); + buffer->resize(file_len); + fread(const_cast(buffer->data()), 1, buffer->size(), file); + fclose(file); + return X_ERROR_SUCCESS; + } else { + return X_ERROR_FILE_NOT_FOUND; + } +} + +X_RESULT ContentManager::SetContentThumbnail(const XCONTENT_DATA& data, + std::vector buffer) { + std::lock_guard lock(content_mutex_); + auto package_path = ResolvePackagePath(data); + if (poly::fs::PathExists(package_path)) { + auto thumb_path = poly::join_paths(package_path, kThumbnailFileName); + auto file = _wfopen(thumb_path.c_str(), L"wb"); + fwrite(buffer.data(), 1, buffer.size(), file); + fclose(file); + return X_ERROR_SUCCESS; + } else { + return X_ERROR_FILE_NOT_FOUND; + } +} + +X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) { + std::lock_guard lock(content_mutex_); + + auto package_path = ResolvePackagePath(data); + if (poly::fs::PathExists(package_path)) { + poly::fs::DeleteFolder(package_path); + return X_ERROR_SUCCESS; + } else { + return X_ERROR_FILE_NOT_FOUND; + } +} + +} // namespace kernel +} // namespace xe diff --git a/src/xenia/kernel/content_manager.h b/src/xenia/kernel/content_manager.h new file mode 100644 index 000000000..a88c46e28 --- /dev/null +++ b/src/xenia/kernel/content_manager.h @@ -0,0 +1,85 @@ +/** + ****************************************************************************** + * 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_CONTENT_MANAGER_H_ +#define XENIA_KERNEL_CONTENT_MANAGER_H_ + +#include +#include +#include +#include +#include + +#include "poly/memory.h" +#include "xenia/common.h" +#include "xenia/xbox.h" + +namespace xe { +namespace kernel { + +class KernelState; + +struct XCONTENT_DATA { + uint32_t device_id; + uint32_t content_type; + std::wstring display_name; // 128 chars + std::string file_name; + XCONTENT_DATA() = default; + XCONTENT_DATA(const uint8_t* ptr) { + device_id = poly::load_and_swap(ptr + 0); + content_type = poly::load_and_swap(ptr + 4); + display_name = poly::load_and_swap(ptr + 8); + file_name = poly::load_and_swap(ptr + 8 + 128 * 2); + } +}; + +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 { + public: + ContentManager(KernelState* kernel_state, std::wstring root_path); + ~ContentManager(); + + std::unique_ptr ResolvePackage(std::string root_name, + const XCONTENT_DATA& data); + + bool ContentExists(const XCONTENT_DATA& data); + X_RESULT CreateContent(std::string root_name, const XCONTENT_DATA& data); + X_RESULT OpenContent(std::string root_name, const XCONTENT_DATA& data); + X_RESULT CloseContent(std::string root_name); + X_RESULT GetContentThumbnail(const XCONTENT_DATA& data, + std::vector* buffer); + X_RESULT SetContentThumbnail(const XCONTENT_DATA& data, + std::vector buffer); + X_RESULT DeleteContent(const XCONTENT_DATA& data); + + private: + std::wstring ResolvePackagePath(const XCONTENT_DATA& data); + + KernelState* kernel_state_; + std::wstring root_path_; + + std::recursive_mutex content_mutex_; + std::unordered_map open_packages_; +}; + +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_CONTENT_MANAGER_H_ diff --git a/src/xenia/kernel/fs/device.h b/src/xenia/kernel/fs/device.h index 2dc2ebee8..52afeadf8 100644 --- a/src/xenia/kernel/fs/device.h +++ b/src/xenia/kernel/fs/device.h @@ -27,6 +27,8 @@ class Device { const std::string& path() const { return path_; } + virtual bool is_read_only() const { return true; } + virtual std::unique_ptr ResolvePath(const char* path) = 0; virtual X_STATUS QueryVolume(XVolumeInfo* out_info, size_t length); diff --git a/src/xenia/kernel/fs/devices/host_path_device.cc b/src/xenia/kernel/fs/devices/host_path_device.cc index 056f6dfb8..03efc1b97 100644 --- a/src/xenia/kernel/fs/devices/host_path_device.cc +++ b/src/xenia/kernel/fs/devices/host_path_device.cc @@ -17,8 +17,8 @@ namespace kernel { namespace fs { HostPathDevice::HostPathDevice(const std::string& path, - const std::wstring& local_path) - : Device(path), local_path_(local_path) {} + const std::wstring& local_path, bool read_only) + : Device(path), local_path_(local_path), read_only_(read_only) {} HostPathDevice::~HostPathDevice() {} diff --git a/src/xenia/kernel/fs/devices/host_path_device.h b/src/xenia/kernel/fs/devices/host_path_device.h index 0dc6d5bff..adbd1d572 100644 --- a/src/xenia/kernel/fs/devices/host_path_device.h +++ b/src/xenia/kernel/fs/devices/host_path_device.h @@ -21,13 +21,17 @@ namespace fs { class HostPathDevice : public Device { public: - HostPathDevice(const std::string& path, const std::wstring& local_path); + HostPathDevice(const std::string& path, const std::wstring& local_path, + bool read_only); ~HostPathDevice() override; + bool is_read_only() const { return read_only_; } + std::unique_ptr ResolvePath(const char* path) override; private: std::wstring local_path_; + bool read_only_; }; } // namespace fs diff --git a/src/xenia/kernel/fs/devices/host_path_entry.cc b/src/xenia/kernel/fs/devices/host_path_entry.cc index 56f04e3d5..3eddc46cf 100644 --- a/src/xenia/kernel/fs/devices/host_path_entry.cc +++ b/src/xenia/kernel/fs/devices/host_path_entry.cc @@ -140,7 +140,7 @@ X_STATUS HostPathEntry::Open(KernelState* kernel_state, Mode mode, bool async, DWORD desired_access = mode == Mode::READ ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); DWORD share_mode = FILE_SHARE_READ; - DWORD creation_disposition = OPEN_EXISTING; + DWORD creation_disposition = mode == Mode::READ ? OPEN_EXISTING : OPEN_ALWAYS; DWORD flags_and_attributes = async ? FILE_FLAG_OVERLAPPED : 0; HANDLE file = CreateFile(local_path_.c_str(), desired_access, share_mode, NULL, diff --git a/src/xenia/kernel/fs/devices/host_path_file.cc b/src/xenia/kernel/fs/devices/host_path_file.cc index 3d52d818c..dbf5f5a6a 100644 --- a/src/xenia/kernel/fs/devices/host_path_file.cc +++ b/src/xenia/kernel/fs/devices/host_path_file.cc @@ -69,6 +69,23 @@ X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length, } } +X_STATUS HostPathFile::WriteSync(const void* buffer, size_t buffer_length, + size_t byte_offset, + size_t* out_bytes_written) { + OVERLAPPED overlapped; + overlapped.Pointer = (PVOID)byte_offset; + overlapped.hEvent = NULL; + DWORD bytes_written = 0; + BOOL wrote = WriteFile(file_handle_, buffer, (DWORD)buffer_length, + &bytes_written, &overlapped); + if (wrote) { + *out_bytes_written = bytes_written; + return X_STATUS_SUCCESS; + } else { + return X_STATUS_END_OF_FILE; + } +} + } // namespace fs } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/fs/devices/host_path_file.h b/src/xenia/kernel/fs/devices/host_path_file.h index 5e66249ce..e30437090 100644 --- a/src/xenia/kernel/fs/devices/host_path_file.h +++ b/src/xenia/kernel/fs/devices/host_path_file.h @@ -41,6 +41,8 @@ class HostPathFile : public XFile { protected: X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read) override; + X_STATUS WriteSync(const void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_written) override; private: HostPathEntry* entry_; diff --git a/src/xenia/kernel/fs/entry.cc b/src/xenia/kernel/fs/entry.cc index 80d4e78a9..3989e0992 100644 --- a/src/xenia/kernel/fs/entry.cc +++ b/src/xenia/kernel/fs/entry.cc @@ -29,6 +29,10 @@ Entry::Entry(Device* device, const std::string& path) Entry::~Entry() = default; +bool Entry::is_read_only() const { + return device_->is_read_only(); +} + } // namespace fs } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/fs/entry.h b/src/xenia/kernel/fs/entry.h index bb3e9dc5a..436f7f03b 100644 --- a/src/xenia/kernel/fs/entry.h +++ b/src/xenia/kernel/fs/entry.h @@ -61,6 +61,8 @@ class Entry { const std::string& absolute_path() const { return absolute_path_; } const std::string& name() const { return name_; } + bool is_read_only() const; + virtual X_STATUS QueryInfo(XFileInfo* out_info) = 0; virtual X_STATUS QueryDirectory(XDirectoryInfo* out_info, size_t length, const char* file_name, bool restart) = 0; diff --git a/src/xenia/kernel/fs/filesystem.cc b/src/xenia/kernel/fs/filesystem.cc index f56a91ff9..c2ce3d00e 100644 --- a/src/xenia/kernel/fs/filesystem.cc +++ b/src/xenia/kernel/fs/filesystem.cc @@ -74,7 +74,7 @@ int FileSystem::InitializeFromPath(fs::FileSystemType type, // Register the local directory in the virtual filesystem. int result_code = RegisterHostPathDevice( - "\\Device\\Harddisk1\\Partition0", parent_path); + "\\Device\\Harddisk1\\Partition0", parent_path, true); if (result_code) { XELOGE("Unable to mount local directory"); return result_code; @@ -108,8 +108,9 @@ int FileSystem::RegisterDevice(const std::string& path, Device* device) { } int FileSystem::RegisterHostPathDevice(const std::string& path, - const std::wstring& local_path) { - Device* device = new HostPathDevice(path, local_path); + const std::wstring& local_path, + bool read_only) { + Device* device = new HostPathDevice(path, local_path, read_only); return RegisterDevice(path, device); } diff --git a/src/xenia/kernel/fs/filesystem.h b/src/xenia/kernel/fs/filesystem.h index 6716c3273..b434e6b60 100644 --- a/src/xenia/kernel/fs/filesystem.h +++ b/src/xenia/kernel/fs/filesystem.h @@ -40,7 +40,8 @@ class FileSystem { int RegisterDevice(const std::string& path, Device* device); int RegisterHostPathDevice(const std::string& path, - const std::wstring& local_path); + const std::wstring& local_path, + bool read_only); int RegisterDiscImageDevice(const std::string& path, const std::wstring& local_path); int RegisterSTFSContainerDevice(const std::string& path, diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 70f53222a..e93f9e96c 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -9,6 +9,8 @@ #include "xenia/kernel/kernel_state.h" +#include + #include "xenia/emulator.h" #include "xenia/kernel/dispatcher.h" #include "xenia/kernel/xam_module.h" @@ -22,6 +24,9 @@ #include "xenia/kernel/objects/xthread.h" #include "xenia/kernel/objects/xuser_module.h" +DEFINE_string(content_root, "content", + "Root path for content (save/etc) storage."); + namespace xe { namespace kernel { @@ -45,6 +50,10 @@ KernelState::KernelState(Emulator* emulator) app_manager_ = std::make_unique(); user_profile_ = std::make_unique(); + auto content_root = poly::to_wstring(FLAGS_content_root); + content_root = poly::to_absolute_path(content_root); + content_manager_ = std::make_unique(this, content_root); + object_table_ = new ObjectTable(); assert_null(shared_kernel_state_); @@ -70,6 +79,11 @@ KernelState::~KernelState() { KernelState* KernelState::shared() { return shared_kernel_state_; } +uint32_t KernelState::title_id() const { + assert_not_null(executable_module_); + return executable_module_->xex_header()->execution_info.title_id; +} + void KernelState::RegisterModule(XModule* module) {} void KernelState::UnregisterModule(XModule* module) {} diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index ebe668f44..8aebeac24 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -16,6 +16,7 @@ #include "xenia/common.h" #include "xenia/export_resolver.h" #include "xenia/kernel/app.h" +#include "xenia/kernel/content_manager.h" #include "xenia/kernel/fs/filesystem.h" #include "xenia/kernel/object_table.h" #include "xenia/kernel/user_profile.h" @@ -49,11 +50,14 @@ class KernelState { Memory* memory() const { return memory_; } cpu::Processor* processor() const { return processor_; } fs::FileSystem* file_system() const { return file_system_; } + + uint32_t title_id() const; Dispatcher* dispatcher() const { return dispatcher_; } XAppManager* app_manager() const { return app_manager_.get(); } UserProfile* user_profile() const { return user_profile_.get(); } + ContentManager* content_manager() const { return content_manager_.get(); } ObjectTable* object_table() const { return object_table_; } std::mutex& object_mutex() { return object_mutex_; } @@ -90,6 +94,7 @@ class KernelState { std::unique_ptr app_manager_; std::unique_ptr user_profile_; + std::unique_ptr content_manager_; ObjectTable* object_table_; std::mutex object_mutex_; diff --git a/src/xenia/kernel/objects/xfile.cc b/src/xenia/kernel/objects/xfile.cc index 62feacae3..fb5c87655 100644 --- a/src/xenia/kernel/objects/xfile.cc +++ b/src/xenia/kernel/objects/xfile.cc @@ -53,5 +53,19 @@ X_STATUS XFile::Read(void* buffer, size_t buffer_length, size_t byte_offset, return result; } +X_STATUS XFile::Write(const void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_written) { + if (byte_offset == -1) { + // Write from current position. + byte_offset = position_; + } + X_STATUS result = + WriteSync(buffer, buffer_length, byte_offset, out_bytes_written); + if (XSUCCEEDED(result)) { + position_ += *out_bytes_written; + } + return result; +} + } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/objects/xfile.h b/src/xenia/kernel/objects/xfile.h index 48415b56f..37e86f6a3 100644 --- a/src/xenia/kernel/objects/xfile.h +++ b/src/xenia/kernel/objects/xfile.h @@ -146,12 +146,19 @@ class XFile : public XObject { X_STATUS Read(void* buffer, size_t buffer_length, size_t byte_offset, XAsyncRequest* request); + X_STATUS Write(const void* buffer, size_t buffer_length, size_t byte_offset, + size_t* out_bytes_written); + virtual void* GetWaitHandle(); protected: XFile(KernelState* kernel_state, fs::Mode mode); virtual X_STATUS ReadSync(void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read) = 0; + virtual X_STATUS WriteSync(const void* buffer, size_t buffer_length, + size_t byte_offset, size_t* out_bytes_written) { + return X_STATUS_ACCESS_DENIED; + } private: fs::Mode mode_; diff --git a/src/xenia/kernel/sources.gypi b/src/xenia/kernel/sources.gypi index 760d0a069..db10757d3 100644 --- a/src/xenia/kernel/sources.gypi +++ b/src/xenia/kernel/sources.gypi @@ -5,6 +5,8 @@ 'app.h', 'async_request.cc', 'async_request.h', + 'content_manager.cc', + 'content_manager.h', 'dispatcher.cc', 'dispatcher.h', 'kernel.h', diff --git a/src/xenia/kernel/xam_content.cc b/src/xenia/kernel/xam_content.cc index 3cacead7b..9d1822ea8 100644 --- a/src/xenia/kernel/xam_content.cc +++ b/src/xenia/kernel/xam_content.cc @@ -57,7 +57,7 @@ SHIM_CALL XamShowDeviceSelectorUI_shim(PPCContext* ppc_state, uint32_t device_id_ptr = SHIM_GET_ARG_32(4); uint32_t overlapped_ptr = SHIM_GET_ARG_32(5); - XELOGD("XamShowDeviceSelectorUI(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X)", + XELOGD("XamShowDeviceSelectorUI(%d, %.8X, %.8X, %.8X, %.8X, %.8X)", user_index, content_type, content_flags, total_requested, device_id_ptr, overlapped_ptr); @@ -159,6 +159,29 @@ SHIM_CALL XamContentGetDeviceData_shim(PPCContext* ppc_state, SHIM_SET_RETURN_32(X_ERROR_SUCCESS); } +SHIM_CALL XamContentResolve_shim(PPCContext* ppc_state, KernelState* state) { + uint32_t user_index = SHIM_GET_ARG_32(0); + uint32_t content_data_ptr = SHIM_GET_ARG_32(1); + uint32_t buffer_ptr = SHIM_GET_ARG_32(2); + uint32_t buffer_size = SHIM_GET_ARG_32(3); + uint32_t unk1 = SHIM_GET_ARG_32(4); // 1 + uint32_t unk2 = SHIM_GET_ARG_32(5); // 0 + uint32_t unk3 = SHIM_GET_ARG_32(6); // 0 + + auto content_data = XCONTENT_DATA(SHIM_MEM_ADDR(content_data_ptr)); + + XELOGD("XamContentResolve(%d, %.8X, %.8X, %d, %.8X, %.8X, %.8X)", user_index, + content_data_ptr, buffer_ptr, buffer_size, unk1, unk2, unk3); + + // Result of buffer_ptr is sent to RtlInitAnsiString. + // buffer_size is usually 260 (max path). + // Games expect zero if resolve was successful. + assert_always(); + XELOGW("XamContentResolve unimplemented!"); + + SHIM_SET_RETURN_32(X_ERROR_NOT_FOUND); +} + // http://gameservice.googlecode.com/svn-history/r14/trunk/ContentManager.cpp SHIM_CALL XamContentCreateEnumerator_shim(PPCContext* ppc_state, KernelState* state) { @@ -170,7 +193,7 @@ SHIM_CALL XamContentCreateEnumerator_shim(PPCContext* ppc_state, uint32_t buffer_size_ptr = SHIM_GET_ARG_32(5); uint32_t handle_ptr = SHIM_GET_ARG_32(6); - XELOGD("XamContentCreateEnumerator(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X, %.8X)", + XELOGD("XamContentCreateEnumerator(%d, %.8X, %.8X, %.8X, %.8X, %.8X, %.8X)", user_index, device_id, content_type, content_flags, item_count, buffer_size_ptr, handle_ptr); @@ -194,6 +217,227 @@ SHIM_CALL XamContentCreateEnumerator_shim(PPCContext* ppc_state, SHIM_SET_RETURN_32(X_ERROR_SUCCESS); } +SHIM_CALL XamContentCreate_shim(PPCContext* ppc_state, KernelState* state) { + // uint32_t user_index = SHIM_GET_ARG_32(0); + // uint32_t content_data_ptr = SHIM_GET_ARG_32(1); + XELOGD("XamContentCreate unimplemented"); + assert_always(); +} + +SHIM_CALL XamContentCreateEx_shim(PPCContext* ppc_state, KernelState* state) { + uint32_t user_index = SHIM_GET_ARG_32(0); + uint32_t root_name_ptr = SHIM_GET_ARG_32(1); + uint32_t content_data_ptr = SHIM_GET_ARG_32(2); + uint32_t flags = SHIM_GET_ARG_32(3); + uint32_t disposition_ptr = SHIM_GET_ARG_32(4); + uint32_t license_mask_ptr = SHIM_GET_ARG_32(5); + uint32_t cache_size = SHIM_GET_ARG_32(6); + uint64_t content_size = SHIM_GET_ARG_64(7); + uint32_t sp = (uint32_t)ppc_state->r[1]; + uint32_t overlapped_ptr = SHIM_MEM_32(sp + 0x54); + + auto root_name = + poly::load_and_swap(SHIM_MEM_ADDR(root_name_ptr)); + auto content_data = XCONTENT_DATA(SHIM_MEM_ADDR(content_data_ptr)); + + XELOGD( + "XamContentCreateEx(%d, %.8X(%s), %.8X, %.8X, %.8X, %.8X, %.8X, %.8llX, " + "%.8X)", + user_index, root_name_ptr, root_name.c_str(), content_data_ptr, flags, + disposition_ptr, license_mask_ptr, cache_size, content_size, + overlapped_ptr); + + assert_zero(license_mask_ptr); + + X_RESULT result = X_ERROR_INVALID_PARAMETER; + + auto content_manager = state->content_manager(); + bool create = false; + bool open = false; + switch (flags & 0xF) { + case 1: // CREATE_NEW + // Fail if exists. + if (content_manager->ContentExists(content_data)) { + result = X_ERROR_ALREADY_EXISTS; + } else { + create = true; + } + break; + case 2: // CREATE_ALWAYS + // Overwrite existing, if any. + if (content_manager->ContentExists(content_data)) { + content_manager->DeleteContent(content_data); + create = true; + } else { + create = true; + } + break; + case 3: // OPEN_EXISTING + // Open only if exists. + if (!content_manager->ContentExists(content_data)) { + result = X_ERROR_FILE_NOT_FOUND; + } else { + open = true; + } + break; + case 4: // OPEN_ALWAYS + // Create if needed. + if (!content_manager->ContentExists(content_data)) { + create = true; + } else { + open = true; + } + break; + case 5: // TRUNCATE_EXISTING + // Fail if doesn't exist, if does exist delete and recreate. + if (!content_manager->ContentExists(content_data)) { + result = X_ERROR_FILE_NOT_FOUND; + } else { + content_manager->DeleteContent(content_data); + create = true; + } + break; + default: + assert_unhandled_case(flags & 0xF); + break; + } + + if (disposition_ptr) { + if (overlapped_ptr) { + // If async always set to zero, but don't set to a real value. + SHIM_SET_MEM_32(disposition_ptr, 0); + } else { + SHIM_SET_MEM_32(disposition_ptr, create ? 1 : 2); + } + } + + if (create) { + result = content_manager->CreateContent(root_name, content_data); + } else if (open) { + result = content_manager->OpenContent(root_name, content_data); + } + + if (overlapped_ptr) { + state->CompleteOverlappedImmediate(overlapped_ptr, result); + SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); + } else { + SHIM_SET_RETURN_32(result); + } +} + +SHIM_CALL XamContentClose_shim(PPCContext* ppc_state, KernelState* state) { + uint32_t root_name_ptr = SHIM_GET_ARG_32(0); + uint32_t overlapped_ptr = SHIM_GET_ARG_32(1); + + auto root_name = + poly::load_and_swap(SHIM_MEM_ADDR(root_name_ptr)); + + XELOGD("XamContentClose(%.8X(%s), %.8X)", root_name_ptr, root_name.c_str(), + overlapped_ptr); + + // Closes a previously opened root from XamContentCreate*. + auto result = state->content_manager()->CloseContent(root_name); + + if (overlapped_ptr) { + state->CompleteOverlappedImmediate(overlapped_ptr, result); + SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); + } else { + SHIM_SET_RETURN_32(result); + } +} + +SHIM_CALL XamContentGetThumbnail_shim(PPCContext* ppc_state, + KernelState* state) { + uint32_t user_index = SHIM_GET_ARG_32(0); + uint32_t content_data_ptr = SHIM_GET_ARG_32(1); + uint32_t buffer_ptr = SHIM_GET_ARG_32(2); + uint32_t buffer_size_ptr = SHIM_GET_ARG_32(3); + uint32_t overlapped_ptr = SHIM_GET_ARG_32(4); + + assert_not_zero(buffer_size_ptr); + uint32_t buffer_size = SHIM_MEM_32(buffer_size_ptr); + auto content_data = XCONTENT_DATA(SHIM_MEM_ADDR(content_data_ptr)); + + XELOGD("XamContentGetThumbnail(%d, %.8X, %.8X, %.8X(%d), %.8X)", user_index, + content_data_ptr, buffer_ptr, buffer_size_ptr, buffer_size, + overlapped_ptr); + + // Get thumbnail (if it exists). + std::vector buffer; + auto result = + state->content_manager()->GetContentThumbnail(content_data, &buffer); + + SHIM_SET_MEM_32(buffer_size_ptr, uint32_t(buffer.size())); + + if (XSUCCEEDED(result)) { + // Write data, if we were given a pointer. + // This may have just been a size query. + if (buffer_ptr) { + if (buffer_size < buffer.size()) { + // Dest buffer too small. + result = X_ERROR_INSUFFICIENT_BUFFER; + } else { + // Copy data. + std::memcpy(SHIM_MEM_ADDR(buffer_ptr), buffer.data(), buffer.size()); + } + } + } + + if (overlapped_ptr) { + state->CompleteOverlappedImmediate(overlapped_ptr, result); + SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); + } else { + SHIM_SET_RETURN_32(result); + } +} + +SHIM_CALL XamContentSetThumbnail_shim(PPCContext* ppc_state, + KernelState* state) { + uint32_t user_index = SHIM_GET_ARG_32(0); + uint32_t content_data_ptr = SHIM_GET_ARG_32(1); + uint32_t buffer_ptr = SHIM_GET_ARG_32(2); + uint32_t buffer_size = SHIM_GET_ARG_32(3); + uint32_t overlapped_ptr = SHIM_GET_ARG_32(4); + + auto content_data = XCONTENT_DATA(SHIM_MEM_ADDR(content_data_ptr)); + + XELOGD("XamContentSetThumbnail(%d, %.8X, %.8X, %d, %.8X)", user_index, + content_data_ptr, buffer_ptr, buffer_size, overlapped_ptr); + + // Buffer is PNG data. + auto buffer = std::vector(SHIM_MEM_ADDR(buffer_ptr), + SHIM_MEM_ADDR(buffer_ptr) + buffer_size); + auto result = state->content_manager()->SetContentThumbnail( + content_data, std::move(buffer)); + + if (overlapped_ptr) { + state->CompleteOverlappedImmediate(overlapped_ptr, result); + SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); + } else { + SHIM_SET_RETURN_32(result); + } +} + +SHIM_CALL XamContentDelete_shim(PPCContext* ppc_state, KernelState* state) { + uint32_t user_index = SHIM_GET_ARG_32(0); + uint32_t content_data_ptr = SHIM_GET_ARG_32(1); + uint32_t overlapped_ptr = SHIM_GET_ARG_32(2); + + auto content_data = XCONTENT_DATA(SHIM_MEM_ADDR(content_data_ptr)); + + XELOGD("XamContentDelete(%d, %.8X, %.8X)", user_index, content_data_ptr, + overlapped_ptr); + + auto result = state->content_manager()->DeleteContent(content_data); + + if (overlapped_ptr) { + state->CompleteOverlappedImmediate(overlapped_ptr, result); + SHIM_SET_RETURN_32(X_ERROR_IO_PENDING); + } else { + SHIM_SET_RETURN_32(result); + } +} + } // namespace kernel } // namespace xe @@ -204,5 +448,12 @@ void xe::kernel::xam::RegisterContentExports(ExportResolver* export_resolver, SHIM_SET_MAPPING("xam.xex", XamContentGetDeviceName, state); SHIM_SET_MAPPING("xam.xex", XamContentGetDeviceState, state); SHIM_SET_MAPPING("xam.xex", XamContentGetDeviceData, state); + SHIM_SET_MAPPING("xam.xex", XamContentResolve, state); SHIM_SET_MAPPING("xam.xex", XamContentCreateEnumerator, state); + SHIM_SET_MAPPING("xam.xex", XamContentCreate, state); + SHIM_SET_MAPPING("xam.xex", XamContentCreateEx, state); + SHIM_SET_MAPPING("xam.xex", XamContentClose, state); + SHIM_SET_MAPPING("xam.xex", XamContentGetThumbnail, state); + SHIM_SET_MAPPING("xam.xex", XamContentSetThumbnail, state); + SHIM_SET_MAPPING("xam.xex", XamContentDelete, state); } diff --git a/src/xenia/kernel/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl_io.cc index 6bffae188..3bebc2238 100644 --- a/src/xenia/kernel/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl_io.cc @@ -103,8 +103,11 @@ X_STATUS NtCreateFile(PPCContext* ppc_state, KernelState* state, if (creation_disposition != FileDisposition::X_FILE_OPEN || desired_access & FileAccess::X_GENERIC_WRITE || desired_access & FileAccess::X_GENERIC_ALL) { - // We don't support any write modes. - XELOGW("Attempted to open the file/dir for create/write"); + if (entry->is_read_only()) { + // We don't support any write modes. + XELOGW("Attempted to open the file/dir for create/write"); + desired_access = FileAccess::X_GENERIC_READ; + } } XFile* file = nullptr; @@ -113,7 +116,10 @@ X_STATUS NtCreateFile(PPCContext* ppc_state, KernelState* state, info = X_FILE_DOES_NOT_EXIST; } else { // Open the file/directory. - result = fs->Open(std::move(entry), state, fs::Mode::READ, + result = fs->Open(std::move(entry), state, + desired_access == FileAccess::X_GENERIC_READ + ? fs::Mode::READ + : fs::Mode::READ_WRITE, false, // TODO(benvanik): pick async mode, if needed. &file); } @@ -294,6 +300,92 @@ SHIM_CALL NtReadFile_shim(PPCContext* ppc_state, KernelState* state) { SHIM_SET_RETURN_32(result); } +SHIM_CALL NtWriteFile_shim(PPCContext* ppc_state, KernelState* state) { + uint32_t file_handle = SHIM_GET_ARG_32(0); + uint32_t event_handle = SHIM_GET_ARG_32(1); + uint32_t apc_routine_ptr = SHIM_GET_ARG_32(2); + uint32_t apc_context = SHIM_GET_ARG_32(3); + uint32_t io_status_block_ptr = SHIM_GET_ARG_32(4); + uint32_t buffer = SHIM_GET_ARG_32(5); + uint32_t buffer_length = SHIM_GET_ARG_32(6); + uint32_t byte_offset_ptr = SHIM_GET_ARG_32(7); + size_t byte_offset = byte_offset_ptr ? SHIM_MEM_64(byte_offset_ptr) : 0; + + XELOGD("NtWriteFile(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X, %d, %.8X(%d))", + file_handle, event_handle, apc_routine_ptr, apc_context, + io_status_block_ptr, buffer, buffer_length, byte_offset_ptr, + byte_offset); + + // Async not supported yet. + assert_zero(apc_routine_ptr); + + X_STATUS result = X_STATUS_SUCCESS; + uint32_t info = 0; + + // Grab event to signal. + XEvent* ev = NULL; + bool signal_event = false; + if (event_handle) { + result = state->object_table()->GetObject(event_handle, (XObject**)&ev); + } + + // Grab file. + XFile* file = NULL; + if (XSUCCEEDED(result)) { + result = state->object_table()->GetObject(file_handle, (XObject**)&file); + } + + // Execute write. + if (XSUCCEEDED(result)) { + // Reset event before we begin. + if (ev) { + ev->Reset(); + } + + // TODO(benvanik): async path. + if (true) { + // Synchronous request. + if (!byte_offset_ptr || byte_offset == 0xFFFFFFFFfffffffe) { + // FILE_USE_FILE_POINTER_POSITION + byte_offset = -1; + } + + // Write now. + size_t bytes_written = 0; + result = file->Write(SHIM_MEM_ADDR(buffer), buffer_length, byte_offset, + &bytes_written); + if (XSUCCEEDED(result)) { + info = (int32_t)bytes_written; + } + + // Mark that we should signal the event now. We do this after + // we have written the info out. + signal_event = true; + } else { + // X_STATUS_PENDING if not returning immediately. + result = X_STATUS_PENDING; + info = 0; + } + } + + if (io_status_block_ptr) { + SHIM_SET_MEM_32(io_status_block_ptr, result); // Status + SHIM_SET_MEM_32(io_status_block_ptr + 4, info); // Information + } + + if (file) { + file->Release(); + } + if (ev) { + if (signal_event) { + ev->Set(0, false); + } + ev->Release(); + } + + SHIM_SET_RETURN_32(result); +} + SHIM_CALL NtSetInformationFile_shim(PPCContext* ppc_state, KernelState* state) { uint32_t file_handle = SHIM_GET_ARG_32(0); uint32_t io_status_block_ptr = SHIM_GET_ARG_32(1); @@ -629,6 +721,7 @@ void xe::kernel::xboxkrnl::RegisterIoExports(ExportResolver* export_resolver, SHIM_SET_MAPPING("xboxkrnl.exe", NtCreateFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtOpenFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtReadFile, state); + SHIM_SET_MAPPING("xboxkrnl.exe", NtWriteFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryInformationFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtSetInformationFile, state); SHIM_SET_MAPPING("xboxkrnl.exe", NtQueryFullAttributesFile, state); diff --git a/src/xenia/kernel/xboxkrnl_video.cc b/src/xenia/kernel/xboxkrnl_video.cc index 7d0099ce2..53d3718b4 100644 --- a/src/xenia/kernel/xboxkrnl_video.cc +++ b/src/xenia/kernel/xboxkrnl_video.cc @@ -295,7 +295,7 @@ SHIM_CALL VdInitializeScalerCommandBuffer_shim(PPCContext* ppc_state, // arg8 is in stack! uint32_t sp = (uint32_t)ppc_state->r[1]; // Points to the first 80000000h where the memcpy sources from. - uint32_t dest_ptr = SHIM_MEM_32(sp + 0x64); + uint32_t dest_ptr = SHIM_MEM_32(sp + 0x54); XELOGD( "VdInitializeScalerCommandBuffer(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X, " diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index c91f451c9..2f0c66da8 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -76,14 +76,17 @@ typedef uint32_t X_RESULT; #define X_FACILITY_WIN32 7 #define X_RESULT_FROM_WIN32(x) x //((X_RESULT)(x) <= 0 ? ((X_RESULT)(x)) : ((X_RESULT) (((x) & 0x0000FFFF) | (X_FACILITY_WIN32 << 16) | 0x80000000))) #define X_ERROR_SUCCESS X_RESULT_FROM_WIN32(0x00000000L) +#define X_ERROR_FILE_NOT_FOUND X_RESULT_FROM_WIN32(0x00000002L) #define X_ERROR_ACCESS_DENIED X_RESULT_FROM_WIN32(0x00000005L) #define X_ERROR_INVALID_HANDLE X_RESULT_FROM_WIN32(0x00000006L) #define X_ERROR_NO_MORE_FILES X_RESULT_FROM_WIN32(0x00000018L) #define X_ERROR_INVALID_PARAMETER X_RESULT_FROM_WIN32(0x00000057L) #define X_ERROR_IO_PENDING X_RESULT_FROM_WIN32(0x000003E5L) #define X_ERROR_INSUFFICIENT_BUFFER X_RESULT_FROM_WIN32(0x0000007AL) +#define X_ERROR_INVALID_NAME X_RESULT_FROM_WIN32(0x0000007BL) #define X_ERROR_BAD_ARGUMENTS X_RESULT_FROM_WIN32(0x000000A0L) #define X_ERROR_BUSY X_RESULT_FROM_WIN32(0x000000AAL) +#define X_ERROR_ALREADY_EXISTS X_RESULT_FROM_WIN32(0x000000B7L) #define X_ERROR_DEVICE_NOT_CONNECTED X_RESULT_FROM_WIN32(0x0000048FL) #define X_ERROR_NOT_FOUND X_RESULT_FROM_WIN32(0x00000490L) #define X_ERROR_CANCELLED X_RESULT_FROM_WIN32(0x000004C7L)