diff --git a/Source/Core/DolphinWX/CMakeLists.txt b/Source/Core/DolphinWX/CMakeLists.txt index 31927c0422..7b00f96329 100644 --- a/Source/Core/DolphinWX/CMakeLists.txt +++ b/Source/Core/DolphinWX/CMakeLists.txt @@ -34,6 +34,7 @@ set(GUI_SRCS Debugger/RegisterWindow.cpp Debugger/WatchView.cpp Debugger/WatchWindow.cpp + ISOProperties/FilesystemPanel.cpp ISOProperties/InfoPanel.cpp ISOProperties/ISOProperties.cpp NetPlay/ChangeGameDialog.cpp diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj b/Source/Core/DolphinWX/DolphinWX.vcxproj index a4738e1af5..d88b6f06e0 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj @@ -90,6 +90,7 @@ + @@ -146,6 +147,7 @@ + diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters index eb099a84b3..8ce65746ae 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters @@ -224,6 +224,9 @@ GUI\Config + + GUI\ISOProperties + GUI\ISOProperties @@ -437,6 +440,9 @@ GUI\Config + + GUI\ISOProperties + GUI\ISOProperties diff --git a/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp new file mode 100644 index 0000000000..bd1fc44f3b --- /dev/null +++ b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.cpp @@ -0,0 +1,641 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinWX/ISOProperties/FilesystemPanel.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonPaths.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "DiscIO/Enums.h" +#include "DiscIO/Filesystem.h" +#include "DiscIO/Volume.h" +#include "DiscIO/VolumeCreator.h" +#include "DolphinWX/ISOFile.h" +#include "DolphinWX/WxUtils.h" + +namespace +{ +class WiiPartition final : public wxTreeItemData +{ +public: + WiiPartition(std::unique_ptr volume_, + std::unique_ptr filesystem_) + : volume{std::move(volume_)}, filesystem{std::move(filesystem_)} + { + } + + std::unique_ptr volume; + std::unique_ptr filesystem; +}; + +class IntegrityCheckThread final : public wxThread +{ +public: + explicit IntegrityCheckThread(const WiiPartition& partition) + : wxThread{wxTHREAD_JOINABLE}, m_partition{partition} + { + Create(); + } + + ExitCode Entry() override + { + return reinterpret_cast(m_partition.volume->CheckIntegrity()); + } + +private: + const WiiPartition& m_partition; +}; + +enum : int +{ + ICON_DISC, + ICON_FOLDER, + ICON_FILE +}; + +wxImageList* LoadIconBitmaps(const wxWindow* context) +{ + static constexpr std::array icon_names{ + {"isoproperties_disc", "isoproperties_folder", "isoproperties_file"}}; + + const wxSize icon_size = context->FromDIP(wxSize(16, 16)); + auto* const icon_list = new wxImageList(icon_size.GetWidth(), icon_size.GetHeight()); + + for (const auto& name : icon_names) + { + icon_list->Add( + WxUtils::LoadScaledResourceBitmap(name, context, icon_size, wxDefaultSize, + WxUtils::LSI_SCALE_DOWN | WxUtils::LSI_ALIGN_CENTER)); + } + + return icon_list; +} + +size_t CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent, + const std::vector& file_infos, + const size_t first_index, const size_t last_index) +{ + size_t current_index = first_index; + + while (current_index < last_index) + { + const DiscIO::SFileInfo& file_info = file_infos[current_index]; + std::string file_path = file_info.m_FullPath; + + // Trim the trailing '/' if it exists. + if (file_path.back() == DIR_SEP_CHR) + { + file_path.pop_back(); + } + + // Cut off the path up to the actual filename or folder. + // Say we have "/music/stream/stream1.strm", the result will be "stream1.strm". + const size_t dir_sep_index = file_path.rfind(DIR_SEP_CHR); + if (dir_sep_index != std::string::npos) + { + file_path = file_path.substr(dir_sep_index + 1); + } + + // check next index + if (file_info.IsDirectory()) + { + const wxTreeItemId item = tree_ctrl->AppendItem(parent, StrToWxStr(file_path), ICON_FOLDER); + current_index = CreateDirectoryTree(tree_ctrl, item, file_infos, current_index + 1, + static_cast(file_info.m_FileSize)); + } + else + { + tree_ctrl->AppendItem(parent, StrToWxStr(file_path), ICON_FILE); + current_index++; + } + } + + return current_index; +} + +size_t CreateDirectoryTree(wxTreeCtrl* tree_ctrl, wxTreeItemId parent, + const std::vector& file_infos) +{ + if (file_infos.empty()) + return 0; + + return CreateDirectoryTree(tree_ctrl, parent, file_infos, 1, file_infos.at(0).m_FileSize); +} + +WiiPartition* FindWiiPartition(wxTreeCtrl* tree_ctrl, const wxString& label) +{ + wxTreeItemIdValue cookie; + auto partition = tree_ctrl->GetFirstChild(tree_ctrl->GetRootItem(), cookie); + + while (partition.IsOk()) + { + const wxString partition_label = tree_ctrl->GetItemText(partition); + + if (partition_label == label) + return static_cast(tree_ctrl->GetItemData(partition)); + + partition = tree_ctrl->GetNextSibling(partition); + } + + return nullptr; +} +} // Anonymous namespace + +FilesystemPanel::FilesystemPanel(wxWindow* parent, wxWindowID id, const GameListItem& item, + const std::unique_ptr& opened_iso) + : wxPanel{parent, id}, m_game_list_item{item}, m_opened_iso{opened_iso} +{ + CreateGUI(); + BindEvents(); + PopulateFileSystemTree(); + + m_tree_ctrl->Expand(m_tree_ctrl->GetRootItem()); +} + +FilesystemPanel::~FilesystemPanel() = default; + +void FilesystemPanel::BindEvents() +{ + m_tree_ctrl->Bind(wxEVT_TREE_ITEM_RIGHT_CLICK, &FilesystemPanel::OnRightClickTree, this); + + Bind(wxEVT_MENU, &FilesystemPanel::OnExtractFile, this, ID_EXTRACT_FILE); + Bind(wxEVT_MENU, &FilesystemPanel::OnExtractDirectories, this, ID_EXTRACT_ALL); + Bind(wxEVT_MENU, &FilesystemPanel::OnExtractDirectories, this, ID_EXTRACT_DIR); + Bind(wxEVT_MENU, &FilesystemPanel::OnExtractHeaderData, this, ID_EXTRACT_APPLOADER); + Bind(wxEVT_MENU, &FilesystemPanel::OnExtractHeaderData, this, ID_EXTRACT_DOL); + Bind(wxEVT_MENU, &FilesystemPanel::OnCheckPartitionIntegrity, this, ID_CHECK_INTEGRITY); +} + +void FilesystemPanel::CreateGUI() +{ + m_tree_ctrl = new wxTreeCtrl(this); + m_tree_ctrl->AssignImageList(LoadIconBitmaps(this)); + m_tree_ctrl->AddRoot(_("Disc"), ICON_DISC); + + const auto space_5 = FromDIP(5); + auto* const main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->AddSpacer(space_5); + main_sizer->Add(m_tree_ctrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space_5); + main_sizer->AddSpacer(space_5); + + SetSizer(main_sizer); +} + +void FilesystemPanel::PopulateFileSystemTree() +{ + switch (m_opened_iso->GetVolumeType()) + { + case DiscIO::Platform::GAMECUBE_DISC: + PopulateFileSystemTreeGC(); + break; + + case DiscIO::Platform::WII_DISC: + PopulateFileSystemTreeWii(); + break; + + case DiscIO::Platform::ELF_DOL: + case DiscIO::Platform::NUMBER_OF_PLATFORMS: + case DiscIO::Platform::WII_WAD: + break; + } +} + +void FilesystemPanel::PopulateFileSystemTreeGC() +{ + m_filesystem = DiscIO::CreateFileSystem(m_opened_iso.get()); + if (!m_filesystem) + return; + + CreateDirectoryTree(m_tree_ctrl, m_tree_ctrl->GetRootItem(), m_filesystem->GetFileList()); +} + +void FilesystemPanel::PopulateFileSystemTreeWii() const +{ + u32 partition_count = 0; + + for (u32 group = 0; group < 4; group++) + { + // yes, technically there can be OVER NINE THOUSAND partitions... + for (u32 i = 0; i < 0xFFFFFFFF; i++) + { + auto volume = DiscIO::CreateVolumeFromFilename(m_game_list_item.GetFileName(), group, i); + if (volume == nullptr) + break; + + auto file_system = DiscIO::CreateFileSystem(volume.get()); + if (file_system != nullptr) + { + auto* const partition = new WiiPartition(std::move(volume), std::move(file_system)); + + const wxTreeItemId partition_root = m_tree_ctrl->AppendItem( + m_tree_ctrl->GetRootItem(), wxString::Format(_("Partition %u"), partition_count), + ICON_DISC); + + m_tree_ctrl->SetItemData(partition_root, partition); + CreateDirectoryTree(m_tree_ctrl, partition_root, partition->filesystem->GetFileList()); + + if (partition_count == 1) + m_tree_ctrl->Expand(partition_root); + + partition_count++; + } + } + } +} + +void FilesystemPanel::OnRightClickTree(wxTreeEvent& event) +{ + m_tree_ctrl->SelectItem(event.GetItem()); + + wxMenu menu; + + const auto selection = m_tree_ctrl->GetSelection(); + const auto first_visible_item = m_tree_ctrl->GetFirstVisibleItem(); + const int image_type = m_tree_ctrl->GetItemImage(selection); + + if (image_type == ICON_DISC && first_visible_item != selection) + { + menu.Append(ID_EXTRACT_DIR, _("Extract Partition...")); + } + else if (image_type == ICON_FOLDER) + { + menu.Append(ID_EXTRACT_DIR, _("Extract Directory...")); + } + else if (image_type == ICON_FILE) + { + menu.Append(ID_EXTRACT_FILE, _("Extract File...")); + } + + menu.Append(ID_EXTRACT_ALL, _("Extract All Files...")); + + if (m_opened_iso->GetVolumeType() != DiscIO::Platform::WII_DISC || + (image_type == ICON_DISC && first_visible_item != selection)) + { + menu.AppendSeparator(); + menu.Append(ID_EXTRACT_APPLOADER, _("Extract Apploader...")); + menu.Append(ID_EXTRACT_DOL, _("Extract DOL...")); + } + + if (image_type == ICON_DISC && first_visible_item != selection) + { + menu.AppendSeparator(); + menu.Append(ID_CHECK_INTEGRITY, _("Check Partition Integrity")); + } + + PopupMenu(&menu); + event.Skip(); +} + +void FilesystemPanel::OnExtractFile(wxCommandEvent& WXUNUSED(event)) +{ + const wxString selection_label = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection()); + + const wxString output_file_path = + wxFileSelector(_("Extract File"), wxEmptyString, selection_label, wxEmptyString, + wxGetTranslation(wxALL_FILES), wxFD_SAVE, this); + + if (output_file_path.empty() || selection_label.empty()) + return; + + ExtractSingleFile(output_file_path); +} + +void FilesystemPanel::OnExtractDirectories(wxCommandEvent& event) +{ + const wxString selected_directory_label = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection()); + const wxString extract_path = wxDirSelector(_("Choose the folder to extract to")); + + if (extract_path.empty() || selected_directory_label.empty()) + return; + + switch (event.GetId()) + { + case ID_EXTRACT_ALL: + ExtractAllFiles(extract_path); + break; + case ID_EXTRACT_DIR: + ExtractSingleDirectory(extract_path); + break; + } +} + +void FilesystemPanel::OnExtractHeaderData(wxCommandEvent& event) +{ + DiscIO::IFileSystem* file_system = nullptr; + const wxString path = wxDirSelector(_("Choose the folder to extract to")); + + if (path.empty()) + return; + + if (m_opened_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) + { + const auto* const selection_data = m_tree_ctrl->GetItemData(m_tree_ctrl->GetSelection()); + const auto* const partition = static_cast(selection_data); + + file_system = partition->filesystem.get(); + } + else + { + file_system = m_filesystem.get(); + } + + bool ret = false; + if (event.GetId() == ID_EXTRACT_APPLOADER) + { + ret = file_system->ExportApploader(WxStrToStr(path)); + } + else if (event.GetId() == ID_EXTRACT_DOL) + { + ret = file_system->ExportDOL(WxStrToStr(path)); + } + + if (!ret) + { + WxUtils::ShowErrorDialog( + wxString::Format(_("Failed to extract to %s!"), WxStrToStr(path).c_str())); + } +} + +void FilesystemPanel::OnCheckPartitionIntegrity(wxCommandEvent& WXUNUSED(event)) +{ + // Normally we can't enter this function if we aren't analyzing a Wii disc + // anyway, but let's still check to be sure. + if (m_opened_iso->GetVolumeType() != DiscIO::Platform::WII_DISC) + return; + + wxProgressDialog dialog(_("Checking integrity..."), _("Working..."), 1000, this, + wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH); + + const auto selection = m_tree_ctrl->GetSelection(); + + IntegrityCheckThread thread(*static_cast(m_tree_ctrl->GetItemData(selection))); + thread.Run(); + + while (thread.IsAlive()) + { + dialog.Pulse(); + wxThread::Sleep(50); + } + + dialog.Destroy(); + + if (thread.Wait()) + { + wxMessageBox(_("Integrity check completed. No errors have been found."), + _("Integrity check completed"), wxOK | wxICON_INFORMATION, this); + } + else + { + wxMessageBox(wxString::Format(_("Integrity check for %s failed. The disc image is most " + "likely corrupted or has been patched incorrectly."), + m_tree_ctrl->GetItemText(selection)), + _("Integrity Check Error"), wxOK | wxICON_ERROR, this); + } +} + +void FilesystemPanel::ExtractAllFiles(const wxString& output_folder) +{ + switch (m_opened_iso->GetVolumeType()) + { + case DiscIO::Platform::GAMECUBE_DISC: + ExtractAllFilesGC(output_folder); + break; + + case DiscIO::Platform::WII_DISC: + ExtractAllFilesWii(output_folder); + break; + + case DiscIO::Platform::ELF_DOL: + case DiscIO::Platform::NUMBER_OF_PLATFORMS: + case DiscIO::Platform::WII_WAD: + break; + } +} + +void FilesystemPanel::ExtractAllFilesGC(const wxString& output_folder) +{ + ExtractDirectories("", WxStrToStr(output_folder), m_filesystem.get()); +} + +void FilesystemPanel::ExtractAllFilesWii(const wxString& output_folder) +{ + const wxTreeItemId root = m_tree_ctrl->GetRootItem(); + + wxTreeItemIdValue cookie; + wxTreeItemId item = m_tree_ctrl->GetFirstChild(root, cookie); + + while (item.IsOk()) + { + const auto* const partition = static_cast(m_tree_ctrl->GetItemData(item)); + ExtractDirectories("", WxStrToStr(output_folder), partition->filesystem.get()); + item = m_tree_ctrl->GetNextChild(root, cookie); + } +} + +void FilesystemPanel::ExtractSingleFile(const wxString& output_file_path) const +{ + const auto selection_file_path = BuildFilePathFromSelection(); + + switch (m_opened_iso->GetVolumeType()) + { + case DiscIO::Platform::GAMECUBE_DISC: + ExtractSingleFileGC(selection_file_path, output_file_path); + break; + + case DiscIO::Platform::WII_DISC: + ExtractSingleFileWii(selection_file_path, output_file_path); + break; + + case DiscIO::Platform::ELF_DOL: + case DiscIO::Platform::NUMBER_OF_PLATFORMS: + case DiscIO::Platform::WII_WAD: + break; + } +} + +void FilesystemPanel::ExtractSingleFileGC(const wxString& file_path, + const wxString& output_file_path) const +{ + m_filesystem->ExportFile(WxStrToStr(file_path), WxStrToStr(output_file_path)); +} + +void FilesystemPanel::ExtractSingleFileWii(wxString file_path, + const wxString& output_file_path) const +{ + const size_t slash_index = file_path.find('/'); + const wxString partition_label = file_path.substr(0, slash_index); + const auto* const partition = FindWiiPartition(m_tree_ctrl, partition_label); + + // Remove "Partition x/" + file_path.erase(0, slash_index + 1); + + partition->filesystem->ExportFile(WxStrToStr(file_path), WxStrToStr(output_file_path)); +} + +void FilesystemPanel::ExtractSingleDirectory(const wxString& output_folder) +{ + const wxString directory_path = BuildDirectoryPathFromSelection(); + + switch (m_opened_iso->GetVolumeType()) + { + case DiscIO::Platform::GAMECUBE_DISC: + ExtractSingleDirectoryGC(directory_path, output_folder); + break; + + case DiscIO::Platform::WII_DISC: + ExtractSingleDirectoryWii(directory_path, output_folder); + break; + + case DiscIO::Platform::ELF_DOL: + case DiscIO::Platform::NUMBER_OF_PLATFORMS: + case DiscIO::Platform::WII_WAD: + break; + } +} + +void FilesystemPanel::ExtractSingleDirectoryGC(const wxString& directory_path, + const wxString& output_folder) +{ + ExtractDirectories(WxStrToStr(directory_path), WxStrToStr(output_folder), m_filesystem.get()); +} + +void FilesystemPanel::ExtractSingleDirectoryWii(wxString directory_path, + const wxString& output_folder) +{ + const size_t slash_index = directory_path.find('/'); + const wxString partition_label = directory_path.substr(0, slash_index); + const auto* const partition = FindWiiPartition(m_tree_ctrl, partition_label); + + // Remove "Partition x/" + directory_path.erase(0, slash_index + 1); + + ExtractDirectories(WxStrToStr(directory_path), WxStrToStr(output_folder), + partition->filesystem.get()); +} + +void FilesystemPanel::ExtractDirectories(const std::string& full_path, + const std::string& output_folder, + DiscIO::IFileSystem* filesystem) +{ + const std::vector& fst = filesystem->GetFileList(); + + u32 index = 0; + u32 size = 0; + + // Extract all + if (full_path.empty()) + { + size = static_cast(fst.size()); + + filesystem->ExportApploader(output_folder); + if (m_opened_iso->GetVolumeType() == DiscIO::Platform::GAMECUBE_DISC) + filesystem->ExportDOL(output_folder); + } + else + { + // Look for the dir we are going to extract + for (index = 0; index < fst.size(); ++index) + { + if (fst[index].m_FullPath == full_path) + { + INFO_LOG(DISCIO, "Found the directory at %u", index); + size = static_cast(fst[index].m_FileSize); + break; + } + } + + INFO_LOG(DISCIO, "Directory found from %u to %u\nextracting to: %s", index, size, + output_folder.c_str()); + } + + const auto dialog_title = (index != 0) ? _("Extracting Directory") : _("Extracting All Files"); + wxProgressDialog dialog(dialog_title, _("Extracting..."), static_cast(size - 1), this, + wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | + wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH); + + // Extraction + for (u32 i = index; i < size; i++) + { + dialog.SetTitle(wxString::Format( + "%s : %u%%", dialog_title.c_str(), + static_cast((static_cast(i - index) / static_cast(size - index)) * + 100))); + + dialog.Update(i, wxString::Format(_("Extracting %s"), StrToWxStr(fst[i].m_FullPath))); + + if (dialog.WasCancelled()) + break; + + if (fst[i].IsDirectory()) + { + const std::string export_name = + StringFromFormat("%s/%s/", output_folder.c_str(), fst[i].m_FullPath.c_str()); + INFO_LOG(DISCIO, "%s", export_name.c_str()); + + if (!File::Exists(export_name) && !File::CreateFullPath(export_name)) + { + ERROR_LOG(DISCIO, "Could not create the path %s", export_name.c_str()); + } + else + { + if (!File::IsDirectory(export_name)) + ERROR_LOG(DISCIO, "%s already exists and is not a directory", export_name.c_str()); + + ERROR_LOG(DISCIO, "Folder %s already exists", export_name.c_str()); + } + } + else + { + const std::string export_name = + StringFromFormat("%s/%s", output_folder.c_str(), fst[i].m_FullPath.c_str()); + INFO_LOG(DISCIO, "%s", export_name.c_str()); + + if (!File::Exists(export_name) && !filesystem->ExportFile(fst[i].m_FullPath, export_name)) + { + ERROR_LOG(DISCIO, "Could not export %s", export_name.c_str()); + } + else + { + ERROR_LOG(DISCIO, "%s already exists", export_name.c_str()); + } + } + } +} + +wxString FilesystemPanel::BuildFilePathFromSelection() const +{ + wxString file_path = m_tree_ctrl->GetItemText(m_tree_ctrl->GetSelection()); + + const auto root_node = m_tree_ctrl->GetRootItem(); + auto node = m_tree_ctrl->GetItemParent(m_tree_ctrl->GetSelection()); + + while (node != root_node) + { + file_path = m_tree_ctrl->GetItemText(node) + DIR_SEP_CHR + file_path; + node = m_tree_ctrl->GetItemParent(node); + } + + return file_path; +} + +wxString FilesystemPanel::BuildDirectoryPathFromSelection() const +{ + wxString directory_path = BuildFilePathFromSelection(); + directory_path += DIR_SEP_CHR; + return directory_path; +} diff --git a/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.h b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.h new file mode 100644 index 0000000000..7b40fb36c9 --- /dev/null +++ b/Source/Core/DolphinWX/ISOProperties/FilesystemPanel.h @@ -0,0 +1,76 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +class GameListItem; +class wxTreeCtrl; +class wxTreeEvent; + +namespace DiscIO +{ +class IFileSystem; +class IVolume; +} + +class FilesystemPanel final : public wxPanel +{ +public: + explicit FilesystemPanel(wxWindow* parent, wxWindowID id, const GameListItem& item, + const std::unique_ptr& opened_iso); + ~FilesystemPanel(); + +private: + enum + { + ID_EXTRACT_DIR = 20000, + ID_EXTRACT_ALL, + ID_EXTRACT_FILE, + ID_EXTRACT_APPLOADER, + ID_EXTRACT_DOL, + ID_CHECK_INTEGRITY, + }; + + void CreateGUI(); + void BindEvents(); + + void PopulateFileSystemTree(); + void PopulateFileSystemTreeGC(); + void PopulateFileSystemTreeWii() const; + + void OnRightClickTree(wxTreeEvent&); + void OnExtractFile(wxCommandEvent&); + void OnExtractDirectories(wxCommandEvent&); + void OnExtractHeaderData(wxCommandEvent&); + void OnCheckPartitionIntegrity(wxCommandEvent&); + + void ExtractAllFiles(const wxString& output_folder); + void ExtractAllFilesGC(const wxString& output_folder); + void ExtractAllFilesWii(const wxString& output_folder); + + void ExtractSingleFile(const wxString& output_file_path) const; + void ExtractSingleFileGC(const wxString& file_path, const wxString& output_file_path) const; + void ExtractSingleFileWii(wxString file_path, const wxString& output_file_path) const; + + void ExtractSingleDirectory(const wxString& output_folder); + void ExtractSingleDirectoryGC(const wxString& directory_path, const wxString& output_folder); + void ExtractSingleDirectoryWii(wxString directory_path, const wxString& output_folder); + + void ExtractDirectories(const std::string& full_path, const std::string& output_folder, + DiscIO::IFileSystem* filesystem); + + wxString BuildFilePathFromSelection() const; + wxString BuildDirectoryPathFromSelection() const; + + wxTreeCtrl* m_tree_ctrl; + + const GameListItem& m_game_list_item; + const std::unique_ptr& m_opened_iso; + + std::unique_ptr m_filesystem; +}; diff --git a/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp b/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp index 0b8c00e1b6..fbff389704 100644 --- a/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp +++ b/Source/Core/DolphinWX/ISOProperties/ISOProperties.cpp @@ -4,13 +4,11 @@ #include "DolphinWX/ISOProperties/ISOProperties.h" -#include #include #include #include #include #include -#include #include #include #include @@ -22,25 +20,18 @@ #include #include #include -#include #include #include -#include -#include #include #include -#include #include #include -#include #include #include #include #include #include #include -#include -#include #include #include "Common/CommonPaths.h" @@ -48,14 +39,12 @@ #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/StringUtil.h" -#include "Common/SysConf.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/GeckoCodeConfig.h" #include "Core/PatchEngine.h" #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" -#include "DiscIO/Filesystem.h" #include "DiscIO/Volume.h" #include "DiscIO/VolumeCreator.h" #include "DolphinWX/Cheats/ActionReplayCodesPanel.h" @@ -65,6 +54,7 @@ #include "DolphinWX/Frame.h" #include "DolphinWX/Globals.h" #include "DolphinWX/ISOFile.h" +#include "DolphinWX/ISOProperties/FilesystemPanel.h" #include "DolphinWX/ISOProperties/InfoPanel.h" #include "DolphinWX/Main.h" #include "DolphinWX/PatchAddEdit.h" @@ -197,13 +187,6 @@ EVT_LISTBOX(ID_PATCHES_LIST, CISOProperties::PatchListSelectionChanged) EVT_BUTTON(ID_EDITPATCH, CISOProperties::PatchButtonClicked) EVT_BUTTON(ID_ADDPATCH, CISOProperties::PatchButtonClicked) EVT_BUTTON(ID_REMOVEPATCH, CISOProperties::PatchButtonClicked) -EVT_TREE_ITEM_RIGHT_CLICK(ID_TREECTRL, CISOProperties::OnRightClickOnTree) -EVT_MENU(IDM_EXTRACTFILE, CISOProperties::OnExtractFile) -EVT_MENU(IDM_EXTRACTDIR, CISOProperties::OnExtractDir) -EVT_MENU(IDM_EXTRACTALL, CISOProperties::OnExtractDir) -EVT_MENU(IDM_EXTRACTAPPLOADER, CISOProperties::OnExtractDataFromHeader) -EVT_MENU(IDM_EXTRACTDOL, CISOProperties::OnExtractDataFromHeader) -EVT_MENU(IDM_CHECKINTEGRITY, CISOProperties::CheckPartitionIntegrity) END_EVENT_TABLE() CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* parent, wxWindowID id, @@ -227,58 +210,6 @@ CISOProperties::CISOProperties(const GameListItem& game_list_item, wxWindow* par CreateGUIControls(); LoadGameConfig(); - // Filesystem browser/dumper - // TODO : Should we add a way to browse the wad file ? - if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_WAD) - { - if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - int partition_count = 0; - for (int group = 0; group < 4; group++) - { - for (u32 i = 0; i < 0xFFFFFFFF; - i++) // yes, technically there can be OVER NINE THOUSAND partitions... - { - std::unique_ptr volume( - DiscIO::CreateVolumeFromFilename(OpenGameListItem.GetFileName(), group, i)); - if (volume != nullptr) - { - std::unique_ptr file_system( - DiscIO::CreateFileSystem(volume.get())); - if (file_system != nullptr) - { - WiiPartition* const partition = - new WiiPartition(std::move(volume), std::move(file_system)); - - wxTreeItemId PartitionRoot = m_Treectrl->AppendItem( - RootId, wxString::Format(_("Partition %i"), partition_count), 0, 0); - - m_Treectrl->SetItemData(PartitionRoot, partition); - CreateDirectoryTree(PartitionRoot, partition->FileSystem->GetFileList()); - - if (partition_count == 1) - m_Treectrl->Expand(PartitionRoot); - - partition_count++; - } - } - else - { - break; - } - } - } - } - else - { - m_filesystem = DiscIO::CreateFileSystem(m_open_iso.get()); - if (m_filesystem) - CreateDirectoryTree(RootId, m_filesystem->GetFileList()); - } - - m_Treectrl->Expand(RootId); - } - wxTheApp->Bind(DOLPHIN_EVT_LOCAL_INI_CHANGED, &CISOProperties::OnLocalIniModified, this); } @@ -286,57 +217,6 @@ CISOProperties::~CISOProperties() { } -size_t CISOProperties::CreateDirectoryTree(wxTreeItemId& parent, - const std::vector& fileInfos) -{ - if (fileInfos.empty()) - return 0; - else - return CreateDirectoryTree(parent, fileInfos, 1, fileInfos.at(0).m_FileSize); -} - -size_t CISOProperties::CreateDirectoryTree(wxTreeItemId& parent, - const std::vector& fileInfos, - const size_t _FirstIndex, const size_t _LastIndex) -{ - size_t CurrentIndex = _FirstIndex; - - while (CurrentIndex < _LastIndex) - { - const DiscIO::SFileInfo rFileInfo = fileInfos[CurrentIndex]; - std::string filePath = rFileInfo.m_FullPath; - - // Trim the trailing '/' if it exists. - if (filePath[filePath.length() - 1] == DIR_SEP_CHR) - { - filePath.pop_back(); - } - - // Cut off the path up to the actual filename or folder. - // Say we have "/music/stream/stream1.strm", the result will be "stream1.strm". - size_t dirSepIndex = filePath.find_last_of(DIR_SEP_CHR); - if (dirSepIndex != std::string::npos) - { - filePath = filePath.substr(dirSepIndex + 1); - } - - // check next index - if (rFileInfo.IsDirectory()) - { - wxTreeItemId item = m_Treectrl->AppendItem(parent, StrToWxStr(filePath), 1, 1); - CurrentIndex = - CreateDirectoryTree(item, fileInfos, CurrentIndex + 1, (size_t)rFileInfo.m_FileSize); - } - else - { - m_Treectrl->AppendItem(parent, StrToWxStr(filePath), 2, 2); - CurrentIndex++; - } - } - - return CurrentIndex; -} - long CISOProperties::GetElementStyle(const char* section, const char* key) { // Disable 3rd state if default gameini overrides the setting @@ -554,29 +434,9 @@ void CISOProperties::CreateGUIControls() if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_WAD) { - wxPanel* const filesystem_panel = new wxPanel(m_Notebook, ID_FILESYSTEM); - m_Notebook->AddPage(filesystem_panel, _("Filesystem")); - - // Filesystem icons - wxSize icon_size = FromDIP(wxSize(16, 16)); - wxImageList* const m_iconList = new wxImageList(icon_size.GetWidth(), icon_size.GetHeight()); - static const std::array s_icon_names{ - {"isoproperties_disc", "isoproperties_folder", "isoproperties_file"}}; - for (const auto& name : s_icon_names) - m_iconList->Add( - WxUtils::LoadScaledResourceBitmap(name, this, icon_size, wxDefaultSize, - WxUtils::LSI_SCALE_DOWN | WxUtils::LSI_ALIGN_CENTER)); - - // Filesystem tree - m_Treectrl = new wxTreeCtrl(filesystem_panel, ID_TREECTRL); - m_Treectrl->AssignImageList(m_iconList); - RootId = m_Treectrl->AddRoot(_("Disc"), 0, 0, nullptr); - - wxBoxSizer* sTreePage = new wxBoxSizer(wxVERTICAL); - sTreePage->AddSpacer(space5); - sTreePage->Add(m_Treectrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); - sTreePage->AddSpacer(space5); - filesystem_panel->SetSizer(sTreePage); + m_Notebook->AddPage( + new FilesystemPanel(m_Notebook, ID_FILESYSTEM, OpenGameListItem, m_open_iso), + _("Filesystem")); } wxStdDialogButtonSizer* sButtons = CreateStdDialogButtonSizer(wxOK | wxNO_DEFAULT); @@ -626,313 +486,6 @@ void CISOProperties::OnCloseClick(wxCommandEvent& WXUNUSED(event)) Close(); } -void CISOProperties::OnRightClickOnTree(wxTreeEvent& event) -{ - m_Treectrl->SelectItem(event.GetItem()); - - wxMenu popupMenu; - - if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 && - m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection()) - { - popupMenu.Append(IDM_EXTRACTDIR, _("Extract Partition...")); - } - else if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 1) - { - popupMenu.Append(IDM_EXTRACTDIR, _("Extract Directory...")); - } - else if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 2) - { - popupMenu.Append(IDM_EXTRACTFILE, _("Extract File...")); - } - - popupMenu.Append(IDM_EXTRACTALL, _("Extract All Files...")); - - if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC || - (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 && - m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection())) - { - popupMenu.AppendSeparator(); - popupMenu.Append(IDM_EXTRACTAPPLOADER, _("Extract Apploader...")); - popupMenu.Append(IDM_EXTRACTDOL, _("Extract DOL...")); - } - - if (m_Treectrl->GetItemImage(m_Treectrl->GetSelection()) == 0 && - m_Treectrl->GetFirstVisibleItem() != m_Treectrl->GetSelection()) - { - popupMenu.AppendSeparator(); - popupMenu.Append(IDM_CHECKINTEGRITY, _("Check Partition Integrity")); - } - - PopupMenu(&popupMenu); - - event.Skip(); -} - -void CISOProperties::OnExtractFile(wxCommandEvent& WXUNUSED(event)) -{ - wxString File = m_Treectrl->GetItemText(m_Treectrl->GetSelection()); - - wxString Path = wxFileSelector(_("Export File"), wxEmptyString, File, wxEmptyString, - wxGetTranslation(wxALL_FILES), wxFD_SAVE, this); - - if (!Path || !File) - return; - - while (m_Treectrl->GetItemParent(m_Treectrl->GetSelection()) != m_Treectrl->GetRootItem()) - { - wxString temp = m_Treectrl->GetItemText(m_Treectrl->GetItemParent(m_Treectrl->GetSelection())); - File = temp + DIR_SEP_CHR + File; - - m_Treectrl->SelectItem(m_Treectrl->GetItemParent(m_Treectrl->GetSelection())); - } - - if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - const wxTreeItemId tree_selection = m_Treectrl->GetSelection(); - WiiPartition* partition = - reinterpret_cast(m_Treectrl->GetItemData(tree_selection)); - File.erase(0, m_Treectrl->GetItemText(tree_selection).length() + 1); // Remove "Partition x/" - - partition->FileSystem->ExportFile(WxStrToStr(File), WxStrToStr(Path)); - } - else - { - m_filesystem->ExportFile(WxStrToStr(File), WxStrToStr(Path)); - } -} - -void CISOProperties::ExportDir(const std::string& _rFullPath, const std::string& _rExportFolder, - const WiiPartition* partition) -{ - bool is_wii = m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC; - DiscIO::IFileSystem* const fs = is_wii ? partition->FileSystem.get() : m_filesystem.get(); - - const std::vector& fst = fs->GetFileList(); - - u32 index = 0; - u32 size = 0; - - // Extract all - if (_rFullPath.empty()) - { - index = 0; - size = (u32)fst.size(); - - fs->ExportApploader(_rExportFolder); - if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC) - fs->ExportDOL(_rExportFolder); - } - else - { - // Look for the dir we are going to extract - for (index = 0; index != fst.size(); ++index) - { - if (fst[index].m_FullPath == _rFullPath) - { - INFO_LOG(DISCIO, "Found the directory at %u", index); - size = (u32)fst[index].m_FileSize; - break; - } - } - - INFO_LOG(DISCIO, "Directory found from %u to %u\nextracting to: %s", index, size, - _rExportFolder.c_str()); - } - - wxString dialogTitle = (index != 0) ? _("Extracting Directory") : _("Extracting All Files"); - wxProgressDialog dialog(dialogTitle, _("Extracting..."), size - 1, this, - wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | - wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH); - - // Extraction - for (u32 i = index; i < size; i++) - { - dialog.SetTitle(wxString::Format("%s : %d%%", dialogTitle.c_str(), - (u32)(((float)(i - index) / (float)(size - index)) * 100))); - - dialog.Update(i, wxString::Format(_("Extracting %s"), StrToWxStr(fst[i].m_FullPath))); - - if (dialog.WasCancelled()) - break; - - if (fst[i].IsDirectory()) - { - const std::string exportName = - StringFromFormat("%s/%s/", _rExportFolder.c_str(), fst[i].m_FullPath.c_str()); - INFO_LOG(DISCIO, "%s", exportName.c_str()); - - if (!File::Exists(exportName) && !File::CreateFullPath(exportName)) - { - ERROR_LOG(DISCIO, "Could not create the path %s", exportName.c_str()); - } - else - { - if (!File::IsDirectory(exportName)) - ERROR_LOG(DISCIO, "%s already exists and is not a directory", exportName.c_str()); - - ERROR_LOG(DISCIO, "Folder %s already exists", exportName.c_str()); - } - } - else - { - const std::string exportName = - StringFromFormat("%s/%s", _rExportFolder.c_str(), fst[i].m_FullPath.c_str()); - INFO_LOG(DISCIO, "%s", exportName.c_str()); - - if (!File::Exists(exportName) && !fs->ExportFile(fst[i].m_FullPath, exportName)) - { - ERROR_LOG(DISCIO, "Could not export %s", exportName.c_str()); - } - else - { - ERROR_LOG(DISCIO, "%s already exists", exportName.c_str()); - } - } - } -} - -void CISOProperties::OnExtractDir(wxCommandEvent& event) -{ - wxString Directory = m_Treectrl->GetItemText(m_Treectrl->GetSelection()); - wxString Path = wxDirSelector(_("Choose the folder to extract to")); - - if (!Path || !Directory) - return; - - if (event.GetId() == IDM_EXTRACTALL) - { - if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - wxTreeItemIdValue cookie; - wxTreeItemId root = m_Treectrl->GetRootItem(); - wxTreeItemId item = m_Treectrl->GetFirstChild(root, cookie); - while (item.IsOk()) - { - ExportDir("", WxStrToStr(Path), - reinterpret_cast(m_Treectrl->GetItemData(item))); - item = m_Treectrl->GetNextChild(root, cookie); - } - } - else - { - ExportDir("", WxStrToStr(Path)); - } - - return; - } - - while (m_Treectrl->GetItemParent(m_Treectrl->GetSelection()) != m_Treectrl->GetRootItem()) - { - wxString temp = m_Treectrl->GetItemText(m_Treectrl->GetItemParent(m_Treectrl->GetSelection())); - Directory = temp + DIR_SEP_CHR + Directory; - - m_Treectrl->SelectItem(m_Treectrl->GetItemParent(m_Treectrl->GetSelection())); - } - - Directory += DIR_SEP_CHR; - - if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - const wxTreeItemId tree_selection = m_Treectrl->GetSelection(); - WiiPartition* partition = - reinterpret_cast(m_Treectrl->GetItemData(tree_selection)); - Directory.erase(0, - m_Treectrl->GetItemText(tree_selection).length() + 1); // Remove "Partition x/" - - ExportDir(WxStrToStr(Directory), WxStrToStr(Path), partition); - } - else - { - ExportDir(WxStrToStr(Directory), WxStrToStr(Path)); - } -} - -void CISOProperties::OnExtractDataFromHeader(wxCommandEvent& event) -{ - DiscIO::IFileSystem* FS = nullptr; - wxString Path = wxDirSelector(_("Choose the folder to extract to")); - - if (Path.empty()) - return; - - if (m_open_iso->GetVolumeType() == DiscIO::Platform::WII_DISC) - { - WiiPartition* partition = - reinterpret_cast(m_Treectrl->GetItemData(m_Treectrl->GetSelection())); - FS = partition->FileSystem.get(); - } - else - { - FS = m_filesystem.get(); - } - - bool ret = false; - if (event.GetId() == IDM_EXTRACTAPPLOADER) - { - ret = FS->ExportApploader(WxStrToStr(Path)); - } - else if (event.GetId() == IDM_EXTRACTDOL) - { - ret = FS->ExportDOL(WxStrToStr(Path)); - } - - if (!ret) - WxUtils::ShowErrorDialog( - wxString::Format(_("Failed to extract to %s!"), WxStrToStr(Path).c_str())); -} - -class IntegrityCheckThread : public wxThread -{ -public: - IntegrityCheckThread(const WiiPartition& Partition) - : wxThread(wxTHREAD_JOINABLE), m_Partition(Partition) - { - Create(); - } - - ExitCode Entry() override { return (ExitCode)m_Partition.Partition->CheckIntegrity(); } -private: - const WiiPartition& m_Partition; -}; - -void CISOProperties::CheckPartitionIntegrity(wxCommandEvent& event) -{ - // Normally we can't enter this function if we aren't analyzing a Wii disc - // anyway, but let's still check to be sure. - if (m_open_iso->GetVolumeType() != DiscIO::Platform::WII_DISC) - return; - - wxProgressDialog dialog(_("Checking integrity..."), _("Working..."), 1000, this, - wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_SMOOTH); - - WiiPartition* partition = - reinterpret_cast(m_Treectrl->GetItemData(m_Treectrl->GetSelection())); - IntegrityCheckThread thread(*partition); - thread.Run(); - - while (thread.IsAlive()) - { - dialog.Pulse(); - wxThread::Sleep(50); - } - - dialog.Destroy(); - - if (!thread.Wait()) - { - wxMessageBox(wxString::Format(_("Integrity check for %s failed. The disc image is most " - "likely corrupted or has been patched incorrectly."), - m_Treectrl->GetItemText(m_Treectrl->GetSelection())), - _("Integrity Check Error"), wxOK | wxICON_ERROR, this); - } - else - { - wxMessageBox(_("Integrity check completed. No errors have been found."), - _("Integrity check completed"), wxOK | wxICON_INFORMATION, this); - } -} - void CISOProperties::OnEmustateChanged(wxCommandEvent& event) { EmuIssues->Enable(event.GetSelection() != 0); diff --git a/Source/Core/DolphinWX/ISOProperties/ISOProperties.h b/Source/Core/DolphinWX/ISOProperties/ISOProperties.h index 10f1cab115..0d244cea88 100644 --- a/Source/Core/DolphinWX/ISOProperties/ISOProperties.h +++ b/Source/Core/DolphinWX/ISOProperties/ISOProperties.h @@ -14,11 +14,11 @@ #include #include "Common/IniFile.h" -#include "DiscIO/Filesystem.h" -#include "DiscIO/Volume.h" #include "DolphinWX/ISOFile.h" #include "DolphinWX/PatchAddEdit.h" +class ActionReplayCodesPanel; +class CheatWarningMessage; class DolphinSlider; class wxButton; class wxCheckBox; @@ -27,32 +27,16 @@ class wxChoice; class wxSpinCtrl; class wxStaticBitmap; class wxTextCtrl; -class wxTreeCtrl; namespace DiscIO { -enum class Language; +class IVolume; } + namespace Gecko { class CodeConfigPanel; } -class ActionReplayCodesPanel; -class CheatWarningMessage; -class GameListItem; - -class WiiPartition final : public wxTreeItemData -{ -public: - WiiPartition(std::unique_ptr partition, - std::unique_ptr file_system) - : Partition(std::move(partition)), FileSystem(std::move(file_system)) - { - } - - std::unique_ptr Partition; - std::unique_ptr FileSystem; -}; struct PHackData { @@ -77,7 +61,6 @@ private: DECLARE_EVENT_TABLE(); std::unique_ptr m_open_iso; - std::unique_ptr m_filesystem; std::vector onFrame; PHackData m_PHack_Data; @@ -104,9 +87,6 @@ private: wxButton* EditPatch; wxButton* RemovePatch; - wxTreeCtrl* m_Treectrl; - wxTreeItemId RootId; - ActionReplayCodesPanel* m_ar_code_panel; Gecko::CodeConfigPanel* m_geckocode_panel; @@ -115,9 +95,7 @@ private: enum { - ID_TREECTRL = 1000, - - ID_NOTEBOOK, + ID_NOTEBOOK = 1000, ID_GAMECONFIG, ID_PATCH_PAGE, ID_ARCODE_PAGE, @@ -147,13 +125,6 @@ private: ID_DEPTHPERCENTAGE, ID_CONVERGENCE, ID_MONODEPTH, - - IDM_EXTRACTDIR, - IDM_EXTRACTALL, - IDM_EXTRACTFILE, - IDM_EXTRACTAPPLOADER, - IDM_EXTRACTDOL, - IDM_CHECKINTEGRITY, }; void LaunchExternalEditor(const std::string& filename, bool wait_until_closed); @@ -165,23 +136,12 @@ private: void OnShowDefaultConfig(wxCommandEvent& event); void PatchListSelectionChanged(wxCommandEvent& event); void PatchButtonClicked(wxCommandEvent& event); - void OnRightClickOnTree(wxTreeEvent& event); - void OnExtractFile(wxCommandEvent& event); - void OnExtractDir(wxCommandEvent& event); - void OnExtractDataFromHeader(wxCommandEvent& event); - void CheckPartitionIntegrity(wxCommandEvent& event); void OnEmustateChanged(wxCommandEvent& event); void OnCheatCodeToggled(wxCommandEvent& event); void OnChangeTitle(wxCommandEvent& event); const GameListItem OpenGameListItem; - size_t CreateDirectoryTree(wxTreeItemId& parent, const std::vector& fileInfos); - size_t CreateDirectoryTree(wxTreeItemId& parent, const std::vector& fileInfos, - const size_t _FirstIndex, const size_t _LastIndex); - void ExportDir(const std::string& _rFullPath, const std::string& _rExportFilename, - const WiiPartition* partition = nullptr); - IniFile GameIniDefault; IniFile GameIniLocal; std::string GameIniFileLocal;