diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index fd47e6018..6826fc049 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -511,6 +511,9 @@ bool EmulatorWindow::Initialize() { file_menu->AddChild( MenuItem::Create(MenuItem::Type::kString, "&Open...", "Ctrl+O", std::bind(&EmulatorWindow::FileOpen, this))); + file_menu->AddChild( + MenuItem::Create(MenuItem::Type::kString, "Install Content...", + std::bind(&EmulatorWindow::InstallContent, this))); #ifdef DEBUG file_menu->AddChild( MenuItem::Create(MenuItem::Type::kString, "Close", @@ -855,6 +858,35 @@ void EmulatorWindow::FileClose() { } } +void EmulatorWindow::InstallContent() { + std::filesystem::path path; + + auto file_picker = xe::ui::FilePicker::Create(); + file_picker->set_mode(ui::FilePicker::Mode::kOpen); + file_picker->set_type(ui::FilePicker::Type::kFile); + file_picker->set_multi_selection(false); + file_picker->set_title("Select Content Package"); + file_picker->set_extensions({ + {"All Files (*.*)", "*.*"}, + }); + if (file_picker->Show(window_.get())) { + auto selected_files = file_picker->selected_files(); + if (!selected_files.empty()) { + path = selected_files[0]; + } + } + + if (!path.empty()) { + // Normalize the path and make absolute. + auto abs_path = std::filesystem::absolute(path); + auto result = emulator_->InstallContentPackage(abs_path); + if (XFAILED(result)) { + // TODO: Display a message box. + XELOGE("Failed to install content: {:08X}", result); + } + } +} + void EmulatorWindow::ShowContentDirectory() { std::filesystem::path target_path; diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index c57d5f43b..b1c80ed5c 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -130,6 +130,7 @@ class EmulatorWindow { void FileDrop(const std::filesystem::path& filename); void FileOpen(); void FileClose(); + void InstallContent(); void ShowContentDirectory(); void CpuTimeScalarReset(); void CpuTimeScalarSetHalf(); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index cca28982f..b95beda6b 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -372,6 +372,29 @@ X_STATUS Emulator::LaunchStfsContainer(const std::filesystem::path& path) { return CompleteLaunch(path, module_path); } +X_STATUS Emulator::InstallContentPackage(const std::filesystem::path& path) { + std::unique_ptr device = + std::make_unique("", path); + if (!device->Initialize()) { + XELOGE("Failed to initialize device"); + return X_STATUS_INVALID_PARAMETER; + } + + std::filesystem::path installation_path = + content_root() / fmt::format("{:08X}", device->title_id()) / + fmt::format("{:08X}", device->content_type()) / path.filename(); + + if (std::filesystem::exists(installation_path)) { + // TODO(Gliniak): Popup + // Do you want to overwrite already existing data? + } else { + std::filesystem::create_directories(installation_path); + } + + vfs::VirtualFileSystem::ExtractFiles(device.get(), installation_path); + return X_STATUS_SUCCESS; +} + void Emulator::Pause() { if (paused_) { return; diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index a0dbeff25..944a2426e 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -187,6 +187,9 @@ class Emulator { // Launches a game from an STFS container file. X_STATUS LaunchStfsContainer(const std::filesystem::path& path); + // Extract content of package to content specific directory. + X_STATUS InstallContentPackage(const std::filesystem::path& path); + void Pause(); void Resume(); bool is_paused() const { return paused_; } diff --git a/src/xenia/vfs/devices/stfs_container_device.h b/src/xenia/vfs/devices/stfs_container_device.h index 504e22cfd..9698ee953 100644 --- a/src/xenia/vfs/devices/stfs_container_device.h +++ b/src/xenia/vfs/devices/stfs_container_device.h @@ -81,6 +81,9 @@ class StfsContainerDevice : public Device { return files_total_size_ - sizeof(StfsHeader); } + uint32_t title_id() const { return header_.metadata.execution_info.title_id; } + XContentType content_type() const { return header_.metadata.content_type; } + private: const uint32_t kBlocksPerHashLevel[3] = {170, 28900, 4913000}; const uint32_t kEndOfChain = 0xFFFFFF; diff --git a/src/xenia/vfs/vfs_dump.cc b/src/xenia/vfs/vfs_dump.cc index f2416b59a..b1debaae7 100644 --- a/src/xenia/vfs/vfs_dump.cc +++ b/src/xenia/vfs/vfs_dump.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2021 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -19,6 +19,7 @@ #include "xenia/vfs/devices/stfs_container_device.h" #include "xenia/vfs/file.h" +#include "xenia/vfs/virtual_file_system.h" namespace xe { namespace vfs { @@ -46,72 +47,7 @@ int vfs_dump_main(const std::vector& args) { XELOGE("Failed to initialize device"); return 1; } - - // Run through all the files, breadth-first style. - std::queue queue; - auto root = device->ResolvePath("/"); - queue.push(root); - - // Allocate a buffer when needed. - size_t buffer_size = 0; - uint8_t* buffer = nullptr; - - while (!queue.empty()) { - auto entry = queue.front(); - queue.pop(); - for (auto& entry : entry->children()) { - queue.push(entry.get()); - } - - XELOGI("{}", entry->path()); - auto dest_name = base_path / xe::to_path(entry->path()); - if (entry->attributes() & kFileAttributeDirectory) { - std::filesystem::create_directories(dest_name); - continue; - } - - vfs::File* in_file = nullptr; - if (entry->Open(FileAccess::kFileReadData, &in_file) != X_STATUS_SUCCESS) { - continue; - } - - auto file = xe::filesystem::OpenFile(dest_name, "wb"); - if (!file) { - in_file->Destroy(); - continue; - } - - if (entry->can_map()) { - auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead); - fwrite(map->data(), map->size(), 1, file); - map->Close(); - } else { - // Can't map the file into memory. Read it into a temporary buffer. - if (!buffer || entry->size() > buffer_size) { - // Resize the buffer. - if (buffer) { - delete[] buffer; - } - - // Allocate a buffer rounded up to the nearest 512MB. - buffer_size = xe::round_up(entry->size(), 512_MiB); - buffer = new uint8_t[buffer_size]; - } - - size_t bytes_read = 0; - in_file->ReadSync(buffer, entry->size(), 0, &bytes_read); - fwrite(buffer, bytes_read, 1, file); - } - - fclose(file); - in_file->Destroy(); - } - - if (buffer) { - delete[] buffer; - } - - return 0; + return VirtualFileSystem::ExtractFiles(device.get(), base_path); } } // namespace vfs diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index 01f4761e6..0bcedddf7 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -9,6 +9,7 @@ #include "xenia/vfs/virtual_file_system.h" +#include "xenia/base/literals.h" #include "xenia/base/logging.h" #include "xenia/base/string.h" #include "xenia/kernel/xfile.h" @@ -16,6 +17,8 @@ namespace xe { namespace vfs { +using namespace xe::literals; + VirtualFileSystem::VirtualFileSystem() {} VirtualFileSystem::~VirtualFileSystem() { @@ -307,5 +310,74 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, return result; } +int VirtualFileSystem::ExtractFiles(Device* device, + std::filesystem::path base_path) { + // Run through all the files, breadth-first style. + std::queue queue; + auto root = device->ResolvePath("/"); + queue.push(root); + + // Allocate a buffer when needed. + size_t buffer_size = 0; + uint8_t* buffer = nullptr; + + while (!queue.empty()) { + auto entry = queue.front(); + queue.pop(); + for (auto& entry : entry->children()) { + queue.push(entry.get()); + } + + XELOGI("Extracting file: {}", entry->path()); + auto dest_name = base_path / xe::to_path(entry->path()); + if (entry->attributes() & kFileAttributeDirectory) { + std::filesystem::create_directories(dest_name); + continue; + } + + vfs::File* in_file = nullptr; + if (entry->Open(FileAccess::kFileReadData, &in_file) != X_STATUS_SUCCESS) { + continue; + } + + auto file = xe::filesystem::OpenFile(dest_name, "wb"); + if (!file) { + in_file->Destroy(); + continue; + } + + if (entry->can_map()) { + auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead); + fwrite(map->data(), map->size(), 1, file); + map->Close(); + } else { + // Can't map the file into memory. Read it into a temporary buffer. + if (!buffer || entry->size() > buffer_size) { + // Resize the buffer. + if (buffer) { + delete[] buffer; + } + + // Allocate a buffer rounded up to the nearest 512MB. + buffer_size = xe::round_up(entry->size(), 512_MiB); + buffer = new uint8_t[buffer_size]; + } + + size_t bytes_read = 0; + in_file->ReadSync(buffer, entry->size(), 0, &bytes_read); + fwrite(buffer, bytes_read, 1, file); + } + + fclose(file); + in_file->Destroy(); + } + + if (buffer) { + delete[] buffer; + } + + return 0; +} + } // namespace vfs } // namespace xe diff --git a/src/xenia/vfs/virtual_file_system.h b/src/xenia/vfs/virtual_file_system.h index 49e9083dc..6367a4200 100644 --- a/src/xenia/vfs/virtual_file_system.h +++ b/src/xenia/vfs/virtual_file_system.h @@ -47,6 +47,8 @@ class VirtualFileSystem { bool is_non_directory, File** out_file, FileAction* out_action); + static int ExtractFiles(Device* device, std::filesystem::path base_path); + private: xe::global_critical_region global_critical_region_; std::vector> devices_;