From 56703e52e2805bd05752683337343c42eaa10727 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Wed, 26 Mar 2025 20:52:11 +0100 Subject: [PATCH] [UI] Redesigned "Install Content" window - Added ID for each ImGui window - Added filesystem::CreateFolder function --- src/xenia/app/emulator_window.cc | 150 ++++++++++++------ src/xenia/app/emulator_window.h | 28 ++++ src/xenia/base/filesystem.cc | 13 ++ src/xenia/base/filesystem.h | 4 + src/xenia/emulator.cc | 118 +++++++++----- src/xenia/emulator.h | 25 ++- src/xenia/kernel/xam/xam_content.cc | 7 +- src/xenia/kernel/xam/xam_ui.cc | 44 ++--- src/xenia/ui/imgui_dialog.cc | 3 + src/xenia/ui/imgui_dialog.h | 3 + src/xenia/ui/imgui_drawer.cc | 33 ++-- src/xenia/ui/imgui_drawer.h | 4 + .../vfs/devices/xcontent_container_device.cc | 3 +- .../vfs/devices/xcontent_container_device.h | 10 +- src/xenia/vfs/vfs_dump.cc | 5 +- src/xenia/vfs/virtual_file_system.cc | 42 +++-- src/xenia/vfs/virtual_file_system.h | 4 +- src/xenia/xbox.h | 1 + 18 files changed, 346 insertions(+), 151 deletions(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 46558d25f..15aa29daa 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -569,6 +569,93 @@ void EmulatorWindow::DisplayConfigDialog::OnDraw(ImGuiIO& io) { } } +void EmulatorWindow::ContentInstallDialog::OnDraw(ImGuiIO& io) { + ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(20, 20), ImGuiCond_FirstUseEver); + + bool dialog_open = true; + if (!ImGui::Begin( + fmt::format("Installation Progress###{}", window_id_).c_str(), + &dialog_open, + ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_HorizontalScrollbar)) { + Close(); + ImGui::End(); + return; + } + + bool is_everything_installed = true; + for (const auto& entry : *installation_entries_) { + ImGui::BeginTable(fmt::format("table_{}", entry.name_).c_str(), 2); + ImGui::TableNextRow(0); + ImGui::TableSetColumnIndex(0); + if (entry.icon_) { + ImGui::Image(reinterpret_cast(entry.icon_.get()), + ui::default_image_icon_size); + } else { + ImGui::Dummy(ui::default_image_icon_size); + } + ImGui::TableNextColumn(); + + ImGui::Text("Name: %s", entry.name_.c_str()); + ImGui::Text("Installation Path:"); + ImGui::SameLine(); + if (ImGui::TextLink( + xe::path_to_utf8(entry.data_installation_path_).c_str())) { + LaunchFileExplorer(emulator_window_.emulator_->content_root() / + entry.data_installation_path_); + } + + if (entry.content_type_ != xe::XContentType::kInvalid) { + ImGui::Text("Content Type: %s", + XContentTypeMap.at(entry.content_type_).c_str()); + } + + if (entry.installation_result_ != X_ERROR_SUCCESS) { + ImGui::Text("Status: %s (0x%08X)", + entry.installation_error_message_.c_str(), + entry.installation_result_); + } else if (entry.currently_installed_size_ == entry.content_size_ && + entry.installation_result_ == X_ERROR_SUCCESS) { + ImGui::Text("Status: Success"); + } + ImGui::EndTable(); + + if (entry.content_size_ > 0) { + ImGui::ProgressBar(static_cast(entry.currently_installed_size_) / + entry.content_size_); + + if (entry.currently_installed_size_ != entry.content_size_ && + entry.installation_result_ == X_ERROR_SUCCESS) { + is_everything_installed = false; + } + } else { + ImGui::ProgressBar(0.0f); + } + + if (installation_entries_->size() > 1) { + ImGui::Separator(); + } + } + ImGui::Spacing(); + + ImGui::BeginDisabled(!is_everything_installed); + if (ImGui::Button("Close")) { + ImGui::EndDisabled(); + Close(); + ImGui::End(); + return; + } + ImGui::EndDisabled(); + + if (!dialog_open && is_everything_installed) { + Close(); + ImGui::End(); + return; + } + ImGui::End(); +} + bool EmulatorWindow::Initialize() { window_->AddListener(&window_listener_); window_->AddInputListener(&window_listener_, kZOrderEmulatorWindowInput); @@ -1088,62 +1175,27 @@ void EmulatorWindow::InstallContent() { return; } - using content_installation_data = - std::tuple; - std::map> - content_installation_details; + std::shared_ptr> + content_installation_status = + std::make_shared>(); for (const auto& path : paths) { - // Normalize the path and make absolute. - auto abs_path = std::filesystem::absolute(path); - - Emulator::ContentInstallationInfo installation_info; - auto result = emulator_->InstallContentPackage(abs_path, installation_info); - - auto entry = - content_installation_details.find(installation_info.content_type); - - // There is no entry with that specific type of XContent, so we must add it. - if (entry == content_installation_details.end()) { - content_installation_details.insert({installation_info.content_type, {}}); - entry = content_installation_details.find(installation_info.content_type); - }; - - entry->second.push_back({result, installation_info.content_name, - installation_info.installation_path}); + content_installation_status->push_back({path}); } - // Prepare installation process summary message - std::string summary = "Installation result: \n"; + for (auto& entry : *content_installation_status) { + emulator_->ProcessContentPackageHeader(entry.path_, entry); + } - for (const auto& content_type : content_installation_details) { - if (XContentTypeMap.find(content_type.first) != XContentTypeMap.cend()) { - summary += XContentTypeMap.at(content_type.first) + ":\n"; - } else { - summary += "Unknown:\n"; + auto installationThread = std::thread([this, content_installation_status] { + for (auto& entry : *content_installation_status) { + emulator_->InstallContentPackage(entry.path_, entry); } + }); + installationThread.detach(); - for (const auto& content_installation_entry : content_type.second) { - const std::string status = - std::get<0>(content_installation_entry) == X_STATUS_SUCCESS - ? "Success" - : fmt::format("Failed (0x{:08X})", - std::get<0>(content_installation_entry)); - - summary += fmt::format("\t{} - {} => {}\n", status, - std::get<1>(content_installation_entry), - std::get<2>(content_installation_entry)); - } - - summary += "\n"; - } - - if (content_installation_details.count(XContentType::kProfile)) { - emulator_->kernel_state()->xam_state()->profile_manager()->ReloadProfiles(); - } - - xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), - "Content Installation Summary", summary); + new ContentInstallDialog(imgui_drawer_.get(), *this, + content_installation_status); } void EmulatorWindow::ExtractZarchive() { diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 7e58a3372..5b25b92a9 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -167,6 +167,34 @@ class EmulatorWindow { EmulatorWindow& emulator_window_; }; + class ContentInstallDialog final : public ui::ImGuiDialog { + public: + ContentInstallDialog( + ui::ImGuiDrawer* imgui_drawer, EmulatorWindow& emulator_window, + std::shared_ptr> entries) + : ui::ImGuiDialog(imgui_drawer), + emulator_window_(emulator_window), + installation_entries_(entries) { + window_id_ = GetWindowId(); + } + + ~ContentInstallDialog() { + for (auto& entry : *installation_entries_) { + entry.icon_.release(); + } + } + + protected: + void OnDraw(ImGuiIO& io) override; + + private: + uint64_t window_id_; + + EmulatorWindow& emulator_window_; + std::shared_ptr> + installation_entries_; + }; + class DisplayConfigDialog final : public ui::ImGuiDialog { public: DisplayConfigDialog(ui::ImGuiDrawer* imgui_drawer, diff --git a/src/xenia/base/filesystem.cc b/src/xenia/base/filesystem.cc index d8c61935d..408801f28 100644 --- a/src/xenia/base/filesystem.cc +++ b/src/xenia/base/filesystem.cc @@ -24,6 +24,19 @@ bool CreateParentFolder(const std::filesystem::path& path) { return true; } +std::error_code CreateFolder(const std::filesystem::path& path) { + if (std::filesystem::exists(path)) { + return {}; + } + + std::error_code ec; + if (std::filesystem::create_directories(path, ec)) { + return {}; + } + + return ec; +} + std::vector ListDirectories(const std::filesystem::path& path) { std::vector files = ListFiles(path); std::vector directories = {}; diff --git a/src/xenia/base/filesystem.h b/src/xenia/base/filesystem.h index 3596c374c..5dc3de7e7 100644 --- a/src/xenia/base/filesystem.h +++ b/src/xenia/base/filesystem.h @@ -45,6 +45,10 @@ std::filesystem::path GetUserFolder(); // attempting to create it. bool CreateParentFolder(const std::filesystem::path& path); +// Creates folder on specified path. +// If folder already exists it returns success (no error). +std::error_code CreateFolder(const std::filesystem::path& path); + // Creates an empty file at the given path, overwriting if it exists. bool CreateEmptyFile(const std::filesystem::path& path); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 82ca8f1e9..8f5d17074 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -785,53 +785,85 @@ X_STATUS Emulator::DataMigration(const uint64_t xuid) { return X_STATUS_SUCCESS; } -X_STATUS Emulator::InstallContentPackage( - const std::filesystem::path& path, - ContentInstallationInfo& installation_info) { - std::unique_ptr device = +X_STATUS Emulator::ProcessContentPackageHeader( + const std::filesystem::path& path, ContentInstallEntry& installation_info) { + installation_info.name_ = "Invalid Content Package!"; + installation_info.content_type_ = XContentType::kInvalid; + installation_info.data_installation_path_ = xe::path_to_utf8(path.filename()); + + std::unique_ptr device = vfs::XContentContainerDevice::CreateContentDevice("", path); - installation_info.content_name = "Invalid Content Package!"; - installation_info.content_type = static_cast(0); - installation_info.installation_path = xe::path_to_utf8(path.filename()); + if (!device || !device->Initialize()) { + installation_info.installation_result_ = X_STATUS_INVALID_PARAMETER; + installation_info.installation_error_message_ = "Invalid Package Type!"; + XELOGE("Failed to initialize device"); + return X_STATUS_INVALID_PARAMETER; + } + // Always install savefiles to user signed to slot 0. + const auto profile = + kernel_state_->xam_state()->profile_manager()->GetProfile( + static_cast(0)); + + uint64_t xuid = device->xuid(); + if (device->content_type() == + static_cast(XContentType::kSavedGame) && + profile) { + xuid = profile->xuid(); + } + + installation_info.data_installation_path_ = + fmt::format("{:016X}/{:08X}/{:08X}/{}", xuid, device->title_id(), + device->content_type(), path.filename()); + + installation_info.header_installation_path_ = + fmt::format("{:016X}/{:08X}/Headers/{:08X}/{}", xuid, device->title_id(), + device->content_type(), path.filename()); + + installation_info.name_ = + xe::to_utf8(device->content_header().display_name()); + installation_info.content_type_ = + static_cast(device->content_type()); + installation_info.content_size_ = device->data_size(); + + installation_info.icon_ = + imgui_drawer_->LoadImGuiIcon(std::span( + device->GetContainerHeader()->content_metadata.title_thumbnail, + device->GetContainerHeader()->content_metadata.title_thumbnail_size)); + return X_STATUS_SUCCESS; +} + +X_STATUS Emulator::InstallContentPackage( + const std::filesystem::path& path, ContentInstallEntry& installation_info) { + std::unique_ptr device = + vfs::XContentContainerDevice::CreateContentDevice("", path); if (!device || !device->Initialize()) { XELOGE("Failed to initialize device"); return X_STATUS_INVALID_PARAMETER; } - const vfs::XContentContainerDevice* dev = - (vfs::XContentContainerDevice*)device.get(); + const std::filesystem::path installation_path = + content_root() / installation_info.data_installation_path_; - // Always install savefiles to user signed to slot 0. - const auto profile = - kernel_state_->xam_state()->profile_manager()->GetProfile( - static_cast(0)); + const std::filesystem::path header_path = + content_root() / installation_info.header_installation_path_; - uint64_t xuid = dev->xuid(); - if (dev->content_type() == static_cast(XContentType::kSavedGame) && - profile) { - xuid = profile->xuid(); + if (!std::filesystem::exists(content_root())) { + const std::error_code ec = xe::filesystem::CreateFolder(content_root()); + if (ec) { + installation_info.installation_error_message_ = ec.message(); + installation_info.installation_result_ = X_STATUS_ACCESS_DENIED; + return X_STATUS_ACCESS_DENIED; + } } - std::filesystem::path installation_path = - content_root() / fmt::format("{:016X}", xuid) / - fmt::format("{:08X}", dev->title_id()) / - fmt::format("{:08X}", dev->content_type()) / path.filename(); - - std::filesystem::path header_path = - content_root() / fmt::format("{:016X}", xuid) / - fmt::format("{:08X}", dev->title_id()) / "Headers" / - fmt::format("{:08X}", dev->content_type()) / path.filename(); - - installation_info.installation_path = - fmt::format("{:016X}/{:08X}/{:08X}/{}", xuid, dev->title_id(), - dev->content_type(), path.filename()); - - installation_info.content_name = - xe::to_utf8(dev->content_header().display_name()); - installation_info.content_type = - static_cast(dev->content_type()); + const auto disk_space = std::filesystem::space(content_root()); + if (disk_space.available < installation_info.content_size_ * 1.1f) { + installation_info.installation_error_message_ = "Insufficient disk space!"; + installation_info.installation_result_ = X_STATUS_DISK_FULL; + return X_STATUS_DISK_FULL; + } if (std::filesystem::exists(installation_path)) { // TODO(Gliniak): Popup @@ -840,7 +872,9 @@ X_STATUS Emulator::InstallContentPackage( std::error_code error_code; std::filesystem::create_directories(installation_path, error_code); if (error_code) { - installation_info.content_name = "Cannot Create Content Directory!"; + installation_info.installation_error_message_ = + "Cannot Create Content Directory!"; + installation_info.installation_result_ = error_code.value(); return error_code.value(); } } @@ -848,13 +882,19 @@ X_STATUS Emulator::InstallContentPackage( vfs::VirtualFileSystem::ExtractContentHeader(device.get(), header_path); X_STATUS error_code = vfs::VirtualFileSystem::ExtractContentFiles( - device.get(), installation_path); + device.get(), installation_path, + installation_info.currently_installed_size_); if (error_code != X_ERROR_SUCCESS) { return error_code; } + installation_info.currently_installed_size_ = installation_info.content_size_; kernel_state()->BroadcastNotification(kXNotificationLiveContentInstalled, 0); + if (installation_info.content_type_ == XContentType::kProfile) { + kernel_state_->xam_state()->profile_manager()->ReloadProfiles(); + } + return error_code; } @@ -879,7 +919,9 @@ X_STATUS Emulator::ExtractZarchivePackage( } } - return vfs::VirtualFileSystem::ExtractContentFiles(device.get(), extract_dir); + uint64_t progress = 0; + return vfs::VirtualFileSystem::ExtractContentFiles(device.get(), extract_dir, + progress); } X_STATUS Emulator::CreateZarchivePackage( diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 1837f364d..c19328c7c 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -26,6 +26,7 @@ #include "xenia/memory.h" #include "xenia/patcher/patcher.h" #include "xenia/patcher/plugin_loader.h" +#include "xenia/ui/immediate_drawer.h" #include "xenia/vfs/device.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/xbox.h" @@ -234,18 +235,32 @@ class Emulator { X_STATUS LaunchDefaultModule(const std::filesystem::path& path); - struct ContentInstallationInfo { - XContentType content_type; - std::string installation_path; - std::string content_name; + struct ContentInstallEntry { + ContentInstallEntry(std::filesystem::path path) : path_(path) {}; + + std::string name_{}; + std::filesystem::path path_; + std::filesystem::path data_installation_path_; + std::filesystem::path header_installation_path_; + + uint64_t content_size_ = 0; + uint64_t currently_installed_size_ = 0; + X_STATUS installation_result_{}; + std::string installation_error_message_{}; + XContentType content_type_{}; + + std::unique_ptr icon_; }; // Migrates data from content to content/xuid with respect to common data. X_STATUS DataMigration(const uint64_t xuid); + X_STATUS ProcessContentPackageHeader(const std::filesystem::path& path, + ContentInstallEntry& installation_info); + // Extract content of package to content specific directory. X_STATUS InstallContentPackage(const std::filesystem::path& path, - ContentInstallationInfo& installation_info); + ContentInstallEntry& installation_info); // Extract content of zar package to desired directory. X_STATUS ExtractZarchivePackage(const std::filesystem::path& path, diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index a5ea05144..693033a6d 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -661,8 +661,10 @@ dword_result_t XamContentLaunchImageFromFileInternal_entry( const std::filesystem::path host_path = kernel_state()->emulator()->content_root() / entry->name(); if (!std::filesystem::exists(host_path)) { + uint64_t progress = 0; + vfs::VirtualFileSystem::ExtractContentFile( - entry, kernel_state()->emulator()->content_root(), true); + entry, kernel_state()->emulator()->content_root(), progress, true); } auto xam = kernel_state()->GetKernelModule("xam.xex"); @@ -716,8 +718,9 @@ dword_result_t XamContentLaunchImageInternal_entry(lpvoid_t content_data_ptr, kernel_state()->emulator()->content_root() / entry->name(); if (!std::filesystem::exists(host_path)) { + uint64_t progress = 0; kernel_state()->file_system()->ExtractContentFile( - entry, kernel_state()->emulator()->content_root(), true); + entry, kernel_state()->emulator()->content_root(), progress, true); } auto xam = kernel_state()->GetKernelModule("xam.xex"); diff --git a/src/xenia/kernel/xam/xam_ui.cc b/src/xenia/kernel/xam/xam_ui.cc index 1f81de2a6..9e11ff3e2 100644 --- a/src/xenia/kernel/xam/xam_ui.cc +++ b/src/xenia/kernel/xam/xam_ui.cc @@ -58,8 +58,6 @@ namespace xam { extern std::atomic xam_dialogs_shown_; -constexpr ImVec2 default_image_icon_size = ImVec2(75.f, 75.f); - class XamDialog : public xe::ui::ImGuiDialog { public: void set_close_callback(std::function close_callback) { @@ -509,7 +507,8 @@ class GameAchievementsDialog final : public XamDialog { : XamDialog(imgui_drawer), drawing_position_(drawing_position), title_info_(*title_info), - profile_(profile) { + profile_(profile), + window_id_(GetWindowId()) { LoadAchievementsData(); } @@ -616,9 +615,9 @@ class GameAchievementsDialog final : public XamDialog { const auto icon = GetIcon(achievement_entry); if (icon) { ImGui::Image(reinterpret_cast(GetIcon(achievement_entry)), - default_image_icon_size); + ui::default_image_icon_size); } else { - ImGui::Dummy(default_image_icon_size); + ImGui::Dummy(ui::default_image_icon_size); } ImGui::TableNextColumn(); @@ -632,7 +631,7 @@ class GameAchievementsDialog final : public XamDialog { GetAchievementDescription(achievement_entry).c_str()); ImGui::PopTextWrapPos(); - ImGui::SetCursorPosY(start_drawing_pos.y + default_image_icon_size.x - + ImGui::SetCursorPosY(start_drawing_pos.y + ui::default_image_icon_size.x - ImGui::GetTextLineHeight()); if (achievement_entry.IsUnlocked()) { @@ -644,7 +643,8 @@ class GameAchievementsDialog final : public XamDialog { // TODO(Gliniak): There is no easy way to align text to middle, so I have to // do it manually. const float achievement_row_middle_alignment = - ((default_image_icon_size.x / 2.f) - ImGui::GetTextLineHeight() / 2.f) * + ((ui::default_image_icon_size.x / 2.f) - + ImGui::GetTextLineHeight() / 2.f) * 0.85f; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + @@ -667,10 +667,13 @@ class GameAchievementsDialog final : public XamDialog { bool dialog_open = true; - if (!ImGui::Begin(fmt::format("{} Achievements List", - xe::to_utf8(title_info_.title_name)) - .c_str(), - &dialog_open, + std::string title_name = xe::to_utf8(title_info_.title_name); + title_name.erase(std::remove(title_name.begin(), title_name.end(), '\0'), + title_name.end()); + + const std::string window_name = + fmt::format("{} Achievements###{}", title_name, window_id_); + if (!ImGui::Begin(window_name.c_str(), &dialog_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_HorizontalScrollbar)) { @@ -688,7 +691,7 @@ class GameAchievementsDialog final : public XamDialog { if (ImGui::BeginTable("", 3, ImGuiTableFlags_::ImGuiTableFlags_BordersInnerH)) { for (const auto& entry : achievements_info_) { - ImGui::TableNextRow(0, default_image_icon_size.y); + ImGui::TableNextRow(0, ui::default_image_icon_size.y); DrawTitleAchievementInfo(io, entry); } @@ -707,6 +710,7 @@ class GameAchievementsDialog final : public XamDialog { bool show_locked_info_ = false; + uint64_t window_id_; const ImVec2 drawing_position_ = {}; const TitleInfo title_info_; @@ -724,7 +728,8 @@ class GamesInfoDialog final : public XamDialog { drawing_position_(drawing_position), profile_(profile), profile_manager_(kernel_state()->xam_state()->profile_manager()), - dialog_name_(fmt::format("{}'s Games List", profile->name())) { + dialog_name_(fmt::format("{}'s Games List###{}", profile->name(), + GetWindowId())) { LoadProfileGameInfo(imgui_drawer, profile); } @@ -762,9 +767,9 @@ class GamesInfoDialog final : public XamDialog { if (title_icon.count(entry.id)) { ImGui::Image(reinterpret_cast(title_icon.at(entry.id).get()), - default_image_icon_size); + ui::default_image_icon_size); } else { - ImGui::Dummy(default_image_icon_size); + ImGui::Dummy(ui::default_image_icon_size); } // Second Column @@ -779,7 +784,7 @@ class GamesInfoDialog final : public XamDialog { entry.title_earned_gamerscore) .c_str()); - ImGui::SetCursorPosY(start_position.y + default_image_icon_size.y - + ImGui::SetCursorPosY(start_position.y + ui::default_image_icon_size.y - ImGui::GetTextLineHeight()); if (entry.WasTitlePlayed()) { @@ -881,7 +886,7 @@ class GamesInfoDialog final : public XamDialog { if (ImGui::BeginTable("", 2, ImGuiTableFlags_::ImGuiTableFlags_BordersInnerH)) { - ImGui::TableNextRow(0, default_image_icon_size.y); + ImGui::TableNextRow(0, ui::default_image_icon_size.y); for (auto& entry : info_) { std::string filter(title_name_filter_); if (!filter.empty()) { @@ -1564,9 +1569,10 @@ bool xeDrawProfileContent(ui::ImGuiDrawer* imgui_drawer, const uint64_t xuid, // In the future it can be replaced with profile icon. const auto icon = imgui_drawer->GetNotificationIcon(user_index); if (icon && user_index < XUserMaxUserCount) { - ImGui::Image(reinterpret_cast(icon), default_image_icon_size); + ImGui::Image(reinterpret_cast(icon), + ui::default_image_icon_size); } else { - ImGui::Dummy(default_image_icon_size); + ImGui::Dummy(ui::default_image_icon_size); } ImGui::SameLine(); diff --git a/src/xenia/ui/imgui_dialog.cc b/src/xenia/ui/imgui_dialog.cc index 4e940e798..e3e09df53 100644 --- a/src/xenia/ui/imgui_dialog.cc +++ b/src/xenia/ui/imgui_dialog.cc @@ -16,9 +16,12 @@ namespace xe { namespace ui { +uint64_t ImGuiDialog::next_window_id_ = 0; + ImGuiDialog::ImGuiDialog(ImGuiDrawer* imgui_drawer) : imgui_drawer_(imgui_drawer) { imgui_drawer_->AddDialog(this); + next_window_id_++; } ImGuiDialog::~ImGuiDialog() { diff --git a/src/xenia/ui/imgui_dialog.h b/src/xenia/ui/imgui_dialog.h index 976fd1d26..aefa2658c 100644 --- a/src/xenia/ui/imgui_dialog.h +++ b/src/xenia/ui/imgui_dialog.h @@ -40,6 +40,7 @@ class ImGuiDialog { ImGuiDrawer* imgui_drawer() const { return imgui_drawer_; } ImGuiIO& GetIO(); + uint64_t GetWindowId() const { return next_window_id_; } // Closes the dialog and returns to any waiters. void Close(); @@ -48,6 +49,8 @@ class ImGuiDialog { virtual void OnDraw(ImGuiIO& io) {} private: + static uint64_t next_window_id_; + ImGuiDrawer* imgui_drawer_ = nullptr; bool has_close_pending_ = false; std::vector waiting_fences_; diff --git a/src/xenia/ui/imgui_drawer.cc b/src/xenia/ui/imgui_drawer.cc index 70c5d4698..45afc24a0 100644 --- a/src/xenia/ui/imgui_drawer.cc +++ b/src/xenia/ui/imgui_drawer.cc @@ -234,6 +234,25 @@ std::optional ImGuiDrawer::VirtualKeyToImGuiKey(VirtualKey vkey) { } } +std::unique_ptr ImGuiDrawer::LoadImGuiIcon( + std::span data) { + if (!immediate_drawer_) { + return {}; + } + + int width, height, channels; + unsigned char* image_data = + stbi_load_from_memory(data.data(), static_cast(data.size()), &width, + &height, &channels, STBI_rgb_alpha); + if (!image_data) { + return {}; + } + + return immediate_drawer_->CreateTexture( + width, height, ImmediateTextureFilter::kLinear, true, + reinterpret_cast(image_data)); +} + std::map> ImGuiDrawer::LoadIcons( IconsData data) { std::map> icons_; @@ -242,21 +261,9 @@ std::map> ImGuiDrawer::LoadIcons( return icons_; } - int width, height, channels; - for (const auto& icon : data) { - unsigned char* image_data = stbi_load_from_memory( - icon.second.data(), static_cast(icon.second.size()), &width, - &height, &channels, STBI_rgb_alpha); - - if (!image_data) { - continue; - } - icons_[icon.first] = (immediate_drawer_->CreateTexture( - width, height, ImmediateTextureFilter::kLinear, true, - reinterpret_cast(image_data))); + icons_[icon.first] = LoadImGuiIcon(icon.second); } - return icons_; } diff --git a/src/xenia/ui/imgui_drawer.h b/src/xenia/ui/imgui_drawer.h index 08cbf3745..099dfb454 100644 --- a/src/xenia/ui/imgui_drawer.h +++ b/src/xenia/ui/imgui_drawer.h @@ -37,6 +37,8 @@ class Window; using IconsData = std::map>; +constexpr ImVec2 default_image_icon_size = ImVec2(75.f, 75.f); + class ImGuiDrawer : public WindowInputListener, public UIDrawer { public: ImGuiDrawer(Window* window, size_t z_order); @@ -64,6 +66,8 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { void ClearDialogs(); void EnableNotifications(bool enable) { are_notifications_enabled_ = enable; } + std::unique_ptr LoadImGuiIcon( + std::span data); std::map> LoadIcons( IconsData data); diff --git a/src/xenia/vfs/devices/xcontent_container_device.cc b/src/xenia/vfs/devices/xcontent_container_device.cc index 9501f425d..7da3fd860 100644 --- a/src/xenia/vfs/devices/xcontent_container_device.cc +++ b/src/xenia/vfs/devices/xcontent_container_device.cc @@ -15,7 +15,8 @@ namespace xe { namespace vfs { -std::unique_ptr XContentContainerDevice::CreateContentDevice( +std::unique_ptr +XContentContainerDevice::CreateContentDevice( const std::string_view mount_path, const std::filesystem::path& host_path) { if (!std::filesystem::exists(host_path)) { XELOGE("Path to XContent container does not exist: {}", host_path); diff --git a/src/xenia/vfs/devices/xcontent_container_device.h b/src/xenia/vfs/devices/xcontent_container_device.h index 124715056..c44801a43 100644 --- a/src/xenia/vfs/devices/xcontent_container_device.h +++ b/src/xenia/vfs/devices/xcontent_container_device.h @@ -31,7 +31,7 @@ class XContentContainerDevice : public Device { public: constexpr static uint32_t kBlockSize = 0x1000; - static std::unique_ptr CreateContentDevice( + static std::unique_ptr CreateContentDevice( const std::string_view mount_path, const std::filesystem::path& host_path); @@ -74,6 +74,10 @@ class XContentContainerDevice : public Device { return final_license; } + const XContentContainerHeader* GetContainerHeader() const { + return header_.get(); + } + protected: XContentContainerDevice(const std::string_view mount_path, const std::filesystem::path& host_path); @@ -106,10 +110,6 @@ class XContentContainerDevice : public Device { const std::filesystem::path& GetHostPath() const { return host_path_; } - const XContentContainerHeader* GetContainerHeader() const { - return header_.get(); - } - std::string name_; std::filesystem::path host_path_; diff --git a/src/xenia/vfs/vfs_dump.cc b/src/xenia/vfs/vfs_dump.cc index 6aadfb159..552edd1bf 100644 --- a/src/xenia/vfs/vfs_dump.cc +++ b/src/xenia/vfs/vfs_dump.cc @@ -44,7 +44,10 @@ int vfs_dump_main(const std::vector& args) { XELOGE("Failed to initialize device"); return 1; } - return VirtualFileSystem::ExtractContentFiles(device.get(), base_path); + + uint64_t progress = 0; + return VirtualFileSystem::ExtractContentFiles(device.get(), base_path, + progress); } } // namespace vfs diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index 985031ea8..b3b6b00cf 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -335,6 +335,7 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, X_STATUS VirtualFileSystem::ExtractContentFile(Entry* entry, std::filesystem::path base_path, + uint64_t& progress, bool extract_to_root) { // Allocate a buffer when needed. size_t buffer_size = 0; @@ -369,27 +370,33 @@ X_STATUS VirtualFileSystem::ExtractContentFile(Entry* entry, in_file->Destroy(); return 1; } + constexpr size_t write_buffer_size = 4_MiB; if (entry->can_map()) { auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead); - fwrite(map->data(), map->size(), 1, file); + + auto remaining_size = map->size(); + auto offset = 0; + + while (remaining_size > 0) { + fwrite(map->data() + offset, write_buffer_size, 1, file); + offset += write_buffer_size; + remaining_size -= write_buffer_size; + } 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; - } + auto remaining_size = entry->size(); + size_t offset = 0; + buffer = new uint8_t[write_buffer_size]; - // Allocate a buffer rounded up to the nearest 512MB. - buffer_size = xe::round_up(entry->size(), 512_MiB); - buffer = new uint8_t[buffer_size]; + while (remaining_size > 0) { + size_t bytes_read = 0; + in_file->ReadSync(buffer, write_buffer_size, offset, &bytes_read); + fwrite(buffer, bytes_read, 1, file); + offset += bytes_read; + remaining_size -= bytes_read; + progress += bytes_read; } - - size_t bytes_read = 0; - in_file->ReadSync(buffer, entry->size(), 0, &bytes_read); - fwrite(buffer, bytes_read, 1, file); } fclose(file); @@ -401,8 +408,9 @@ X_STATUS VirtualFileSystem::ExtractContentFile(Entry* entry, return 0; } -X_STATUS VirtualFileSystem::ExtractContentFiles( - Device* device, std::filesystem::path base_path) { +X_STATUS VirtualFileSystem::ExtractContentFiles(Device* device, + std::filesystem::path base_path, + uint64_t& progress) { // Run through all the files, breadth-first style. std::queue queue; auto root = device->ResolvePath("/"); @@ -415,7 +423,7 @@ X_STATUS VirtualFileSystem::ExtractContentFiles( queue.push(entry.get()); } - ExtractContentFile(entry, base_path); + ExtractContentFile(entry, base_path, progress); } return X_STATUS_SUCCESS; } diff --git a/src/xenia/vfs/virtual_file_system.h b/src/xenia/vfs/virtual_file_system.h index 46c42c0c1..df3271b59 100644 --- a/src/xenia/vfs/virtual_file_system.h +++ b/src/xenia/vfs/virtual_file_system.h @@ -51,9 +51,11 @@ class VirtualFileSystem { static X_STATUS ExtractContentFile(Entry* entry, std::filesystem::path base_path, + uint64_t& progress, bool extract_to_root = false); static X_STATUS ExtractContentFiles(Device* device, - std::filesystem::path base_path); + std::filesystem::path base_path, + uint64_t& progress); static void ExtractContentHeader(Device* device, std::filesystem::path base_path); diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index 7a206d56d..05abe803e 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -67,6 +67,7 @@ typedef uint32_t X_STATUS; #define X_STATUS_THREAD_IS_TERMINATING ((X_STATUS)0xC000004BL) #define X_STATUS_PROCEDURE_NOT_FOUND ((X_STATUS)0xC000007AL) #define X_STATUS_INVALID_IMAGE_FORMAT ((X_STATUS)0xC000007BL) +#define X_STATUS_DISK_FULL ((X_STATUS)0xC000007FL) #define X_STATUS_INSUFFICIENT_RESOURCES ((X_STATUS)0xC000009AL) #define X_STATUS_MEMORY_NOT_ALLOCATED ((X_STATUS)0xC00000A0L) #define X_STATUS_FILE_IS_A_DIRECTORY ((X_STATUS)0xC00000BAL)