From 2630fa0ce96e561feb2194fa8e827d7778644976 Mon Sep 17 00:00:00 2001 From: spycrab Date: Fri, 25 Aug 2017 20:32:07 +0200 Subject: [PATCH 1/2] Qt: Only show "Filesystem" Tab when game is disc based --- Source/Core/DolphinQt2/Config/PropertiesDialog.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp index 23527880cb..d45bd8e644 100644 --- a/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp +++ b/Source/Core/DolphinQt2/Config/PropertiesDialog.cpp @@ -17,9 +17,14 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia QTabWidget* tab_widget = new QTabWidget(this); InfoWidget* info = new InfoWidget(game); - FilesystemWidget* filesystem = new FilesystemWidget(game); tab_widget->addTab(info, tr("Info")); - tab_widget->addTab(filesystem, tr("Filesystem")); + + if (DiscIO::IsDisc(game.GetPlatformID())) + { + FilesystemWidget* filesystem = new FilesystemWidget(game); + tab_widget->addTab(filesystem, tr("Filesystem")); + } + layout->addWidget(tab_widget); QDialogButtonBox* ok_box = new QDialogButtonBox(QDialogButtonBox::Ok); From 8b346b705878ddbdca28565a45daddcbe49b2508 Mon Sep 17 00:00:00 2001 From: spycrab Date: Fri, 25 Aug 2017 20:32:37 +0200 Subject: [PATCH 2/2] Qt/GameList: Implement "Filesystem" tab --- .../DolphinQt2/Config/FilesystemWidget.cpp | 297 +++++++++++++++++- .../Core/DolphinQt2/Config/FilesystemWidget.h | 31 ++ 2 files changed, 327 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp b/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp index 935d0617a2..7db79c9392 100644 --- a/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp +++ b/Source/Core/DolphinQt2/Config/FilesystemWidget.cpp @@ -4,6 +4,301 @@ #include "DolphinQt2/Config/FilesystemWidget.h" -FilesystemWidget::FilesystemWidget(const GameFile& game) : m_game(game) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DiscIO/DiscExtractor.h" +#include "DiscIO/Enums.h" +#include "DiscIO/Filesystem.h" +#include "DolphinQt2/Resources.h" + +constexpr int ENTRY_PARTITION = Qt::UserRole; +constexpr int ENTRY_NAME = Qt::UserRole + 1; +constexpr int ENTRY_TYPE = Qt::UserRole + 2; + +enum class EntryType { + Disc = -2, + Partition = -1, + File = 0, + Dir = 1 +}; +Q_DECLARE_METATYPE(EntryType); + +FilesystemWidget::FilesystemWidget(const GameFile& game) + : m_game(game), m_volume(DiscIO::CreateVolumeFromFilename(game.GetFilePath().toStdString())) +{ + CreateWidgets(); + ConnectWidgets(); + PopulateView(); +} + +void FilesystemWidget::CreateWidgets() +{ + auto* layout = new QVBoxLayout; + + m_tree_model = new QStandardItemModel(0, 1); + m_tree_view = new QTreeView(this); + m_tree_view->setModel(m_tree_model); + m_tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + m_tree_view->header()->hide(); + +// Windows: Set style to (old) windows, which draws branch lines +#ifdef _WIN32 + if (QApplication::style()->objectName() == QStringLiteral("windowsvista")) + m_tree_view->setStyle(QStyleFactory::create(QStringLiteral("windows"))); +#endif + + layout->addWidget(m_tree_view); + + setLayout(layout); +} + +void FilesystemWidget::ConnectWidgets() +{ + connect(m_tree_view, &QTreeView::customContextMenuRequested, this, + &FilesystemWidget::ShowContextMenu); +} + +void FilesystemWidget::PopulateView() +{ + auto* disc = new QStandardItem(tr("Disc")); + disc->setEditable(false); + disc->setIcon(Resources::GetScaledIcon("isoproperties_disc")); + disc->setData(QVariant::fromValue(EntryType::Disc), ENTRY_TYPE); + m_tree_model->appendRow(disc); + m_tree_view->expand(disc->index()); + + const auto& partitions = m_volume->GetPartitions(); + + for (size_t i = 0; i < partitions.size(); i++) + { + std::unique_ptr file_system( + DiscIO::CreateFileSystem(m_volume.get(), partitions[i])); + + auto* item = new QStandardItem(tr("Partition %1").arg(i)); + item->setEditable(false); + + item->setIcon(Resources::GetScaledIcon("isoproperties_disc")); + item->setData(static_cast(i), ENTRY_PARTITION); + item->setData(QVariant::fromValue(EntryType::Partition), ENTRY_TYPE); + + PopulateDirectory(static_cast(i), item, file_system->GetRoot()); + + disc->appendRow(item); + + if (m_volume->GetGamePartition() == partitions[i]) + m_tree_view->expand(item->index()); + } + + if (partitions.empty()) + { + PopulateDirectory(-1, disc, + DiscIO::CreateFileSystem(m_volume.get(), DiscIO::PARTITION_NONE)->GetRoot()); + } +} + +void FilesystemWidget::PopulateDirectory(int partition_id, QStandardItem* root, + const DiscIO::FileInfo& directory) +{ + for (const auto& info : directory) + { + auto* item = new QStandardItem(QString::fromStdString(info.GetName())); + item->setEditable(false); + item->setIcon(Resources::GetScaledIcon(info.IsDirectory() ? "isoproperties_folder" : + "isoproperties_file")); + + if (info.IsDirectory()) + PopulateDirectory(partition_id, item, info); + + item->setData(partition_id, ENTRY_PARTITION); + item->setData(QString::fromStdString(info.GetPath()), ENTRY_NAME); + item->setData(QVariant::fromValue(info.IsDirectory() ? EntryType::Dir : EntryType::File), + ENTRY_TYPE); + root->appendRow(item); + } +} + +static QString SelectFolder() +{ + return QFileDialog::getExistingDirectory(nullptr, QObject::tr("Choose the folder to extract to")); +} + +void FilesystemWidget::ShowContextMenu(const QPoint&) +{ + auto* selection = m_tree_view->selectionModel(); + if (!selection->hasSelection()) + return; + + auto* item = m_tree_model->itemFromIndex(selection->selectedIndexes()[0]); + + QMenu* menu = new QMenu(this); + + DiscIO::Partition partition = GetPartitionFromID(item->data(ENTRY_PARTITION).toInt()); + QString path = item->data(ENTRY_NAME).toString(); + + EntryType type = item->data(ENTRY_TYPE).value(); + + if ((type == EntryType::Disc && m_volume->GetPartitions().empty()) || + type == EntryType::Partition) + { + menu->addAction(tr("Extract System Data..."), this, [this, partition] { + auto folder = SelectFolder(); + + if (!folder.isEmpty()) + ExtractSystemData(partition, folder); + }); + } + + switch (type) + { + case EntryType::Disc: + menu->addAction(tr("Extract Entire Disc..."), this, [this, partition, path] { + auto folder = SelectFolder(); + + if (folder.isEmpty()) + return; + + if (m_volume->GetPartitions().empty()) + { + ExtractPartition(DiscIO::PARTITION_NONE, folder); + } + else + { + for (auto& p : m_volume->GetPartitions()) + ExtractPartition(p, folder); + } + }); + break; + case EntryType::Partition: + menu->addAction(tr("Extract Entire Partition..."), this, [this, partition] { + auto folder = SelectFolder(); + if (!folder.isEmpty()) + ExtractPartition(partition, folder); + }); + menu->addSeparator(); + menu->addAction(tr("Check Partition Integrity"), this, + [this, partition] { CheckIntegrity(partition); }); + break; + case EntryType::Dir: + menu->addAction(tr("Extract Files..."), this, [this, partition, path] { + auto folder = SelectFolder(); + + if (!folder.isEmpty()) + ExtractDirectory(partition, path, folder); + }); + break; + case EntryType::File: + menu->addAction(tr("Extract File..."), this, [this, partition, path] { + auto dest = QFileDialog::getSaveFileName(this, tr("Save File to")); + + if (!dest.isEmpty()) + ExtractFile(partition, path, dest); + }); + break; + } + + menu->exec(QCursor::pos()); +} + +DiscIO::Partition FilesystemWidget::GetPartitionFromID(int id) +{ + return id == -1 ? DiscIO::PARTITION_NONE : m_volume->GetPartitions()[id]; +} + +void FilesystemWidget::ExtractPartition(const DiscIO::Partition& partition, const QString& out) +{ + ExtractDirectory(partition, QStringLiteral(""), out); + ExtractSystemData(partition, out); +} + +void FilesystemWidget::ExtractSystemData(const DiscIO::Partition& partition, const QString& out) +{ + bool success = DiscIO::ExportSystemData(*m_volume, partition, out.toStdString()); + + if (success) + QMessageBox::information(nullptr, tr("Success"), tr("Successfully extracted system data.")); + else + QMessageBox::critical(nullptr, tr("Error"), tr("Failed to extract system data.")); +} + +void FilesystemWidget::ExtractDirectory(const DiscIO::Partition& partition, const QString path, + const QString& out) +{ + std::unique_ptr filesystem( + DiscIO::CreateFileSystem(m_volume.get(), partition)); + + std::unique_ptr info = filesystem->FindFileInfo(path.toStdString()); + u32 size = info->GetTotalChildren(); + + QProgressDialog* dialog = new QProgressDialog(this); + dialog->setMinimum(0); + dialog->setMaximum(size); + dialog->show(); + + bool all = path.isEmpty(); + + DiscIO::ExportDirectory( + *m_volume, filesystem->GetPartition(), *info, true, path.toStdString(), out.toStdString(), + [all, dialog](const std::string& current) { + dialog->setLabelText( + (all ? QObject::tr("Extracting All Files...") : QObject::tr("Extracting Directory...")) + .append(QStringLiteral(" %1").arg(QString::fromStdString(current)))); + dialog->setValue(dialog->value() + 1); + + QCoreApplication::processEvents(); + return dialog->wasCanceled(); + }); + + dialog->close(); +} + +void FilesystemWidget::ExtractFile(const DiscIO::Partition& partition, const QString& path, + const QString& out) +{ + std::unique_ptr filesystem( + DiscIO::CreateFileSystem(m_volume.get(), partition)); + bool success = DiscIO::ExportFile( + *m_volume, partition, filesystem->FindFileInfo(path.toStdString()).get(), out.toStdString()); + + if (success) + QMessageBox::information(nullptr, tr("Success"), tr("Successfully extracted file.")); + else + QMessageBox::critical(nullptr, tr("Error"), tr("Failed to extract file.")); +} + +void FilesystemWidget::CheckIntegrity(const DiscIO::Partition& partition) +{ + QProgressDialog* dialog = new QProgressDialog(this); + std::future is_valid = std::async( + std::launch::async, [this, partition] { return m_volume->CheckIntegrity(partition); }); + + dialog->setLabelText(tr("Verifying integrity of partition...")); + dialog->setMinimum(0); + dialog->setMaximum(0); + dialog->show(); + + while (is_valid.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) + QCoreApplication::processEvents(); + + dialog->close(); + + if (is_valid.get()) + QMessageBox::information(nullptr, tr("Success"), + tr("Integrity check completed. No errors have been found")); + else + QMessageBox::critical(nullptr, tr("Error"), + tr("Integrity check for partition failed. The disc image is most " + "likely corrupted or has been patched incorrectly.")); } diff --git a/Source/Core/DolphinQt2/Config/FilesystemWidget.h b/Source/Core/DolphinQt2/Config/FilesystemWidget.h index 8cb58fa236..deabc20442 100644 --- a/Source/Core/DolphinQt2/Config/FilesystemWidget.h +++ b/Source/Core/DolphinQt2/Config/FilesystemWidget.h @@ -5,9 +5,21 @@ #pragma once #include +#include +#include "DiscIO/Volume.h" #include "DolphinQt2/GameList/GameFile.h" +class QStandardItem; +class QStandardItemModel; +class QTreeView; + +namespace DiscIO +{ +class FileInfo; +struct Partition; +}; + class FilesystemWidget final : public QWidget { Q_OBJECT @@ -15,5 +27,24 @@ public: explicit FilesystemWidget(const GameFile& game); private: + void CreateWidgets(); + void ConnectWidgets(); + void PopulateView(); + void PopulateDirectory(int partition_id, QStandardItem* root, const DiscIO::FileInfo& directory); + + void ShowContextMenu(const QPoint&); + + void ExtractPartition(const DiscIO::Partition& partition, const QString& out); + void ExtractDirectory(const DiscIO::Partition& partition, const QString path, const QString& out); + void ExtractFile(const DiscIO::Partition& pratition, const QString& path, const QString& out); + void ExtractSystemData(const DiscIO::Partition& partition, const QString& out); + void CheckIntegrity(const DiscIO::Partition& partition); + + DiscIO::Partition GetPartitionFromID(int id); + + QStandardItemModel* m_tree_model; + QTreeView* m_tree_view; + GameFile m_game; + std::unique_ptr m_volume; };