diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 6a124cd03b..0b468df466 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -18,6 +18,8 @@ add_executable(dolphin-emu AboutDialog.h CheatsManager.cpp CheatsManager.h + ConvertDialog.cpp + ConvertDialog.h DiscordHandler.cpp DiscordHandler.h DiscordJoinRequestDialog.cpp diff --git a/Source/Core/DolphinQt/ConvertDialog.cpp b/Source/Core/DolphinQt/ConvertDialog.cpp new file mode 100644 index 0000000000..c3e102bd7c --- /dev/null +++ b/Source/Core/DolphinQt/ConvertDialog.cpp @@ -0,0 +1,217 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt/ConvertDialog.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Assert.h" +#include "DiscIO/Blob.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/ParallelProgressDialog.h" +#include "UICommon/GameFile.h" + +static bool CompressCB(const std::string& text, float percent, void* ptr) +{ + if (ptr == nullptr) + return false; + + auto* progress_dialog = static_cast(ptr); + + progress_dialog->SetValue(percent * 100); + return !progress_dialog->WasCanceled(); +} + +ConvertDialog::ConvertDialog(QList> files, + QWidget* parent) + : QDialog(parent), m_files(std::move(files)) +{ + ASSERT(!m_files.empty()); + + setWindowTitle(tr("Convert")); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + QGridLayout* grid_layout = new QGridLayout; + grid_layout->setColumnStretch(1, 1); + + m_format = new QComboBox; + AddToFormatComboBox(QStringLiteral("ISO"), DiscIO::BlobType::PLAIN); + AddToFormatComboBox(QStringLiteral("GCZ"), DiscIO::BlobType::GCZ); + grid_layout->addWidget(new QLabel(tr("Format:")), 0, 0); + grid_layout->addWidget(m_format, 0, 1); + + QPushButton* convert_button = new QPushButton(tr("Convert")); + + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addLayout(grid_layout); + main_layout->addWidget(convert_button); + + setLayout(main_layout); + + connect(convert_button, &QPushButton::clicked, this, &ConvertDialog::Convert); +} + +void ConvertDialog::AddToFormatComboBox(const QString& name, DiscIO::BlobType format) +{ + if (std::all_of(m_files.begin(), m_files.end(), + [format](const auto& file) { return file->GetBlobType() == format; })) + { + return; + } + + m_format->addItem(name, static_cast(format)); +} + +void ConvertDialog::Convert() +{ + const DiscIO::BlobType format = static_cast(m_format->currentData().toInt()); + + const bool scrub_wii = format == DiscIO::BlobType::GCZ; + + if (scrub_wii && std::any_of(m_files.begin(), m_files.end(), [](const auto& file) { + return file->GetPlatform() == DiscIO::Platform::WiiDisc; + })) + { + ModalMessageBox wii_warning(this); + wii_warning.setIcon(QMessageBox::Warning); + wii_warning.setWindowTitle(tr("Confirm")); + wii_warning.setText(tr("Are you sure?")); + wii_warning.setInformativeText( + tr("Compressing a Wii disc image will irreversibly change the compressed copy by removing " + "padding data. Your disc image will still work. Continue?")); + wii_warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + if (wii_warning.exec() == QMessageBox::No) + return; + } + + QString extension; + QString filter; + switch (format) + { + case DiscIO::BlobType::PLAIN: + extension = QStringLiteral(".iso"); + filter = tr("Uncompressed GC/Wii images (*.iso *.gcm)"); + break; + case DiscIO::BlobType::GCZ: + extension = QStringLiteral(".gcz"); + filter = tr("Compressed GC/Wii images (*.gcz)"); + break; + default: + ASSERT(false); + return; + } + + QString dst_dir; + QString dst_path; + + if (m_files.size() > 1) + { + dst_dir = QFileDialog::getExistingDirectory( + this, tr("Select where you want to save the converted images"), + QFileInfo(QString::fromStdString(m_files[0]->GetFilePath())).dir().absolutePath()); + + if (dst_dir.isEmpty()) + return; + } + else + { + dst_path = QFileDialog::getSaveFileName( + this, tr("Select where you want to save the converted image"), + QFileInfo(QString::fromStdString(m_files[0]->GetFilePath())) + .dir() + .absoluteFilePath( + QFileInfo(QString::fromStdString(m_files[0]->GetFilePath())).completeBaseName()) + .append(extension), + filter); + + if (dst_path.isEmpty()) + return; + } + + for (const auto& file : m_files) + { + const auto original_path = file->GetFilePath(); + if (m_files.size() > 1) + { + dst_path = + QDir(dst_dir) + .absoluteFilePath(QFileInfo(QString::fromStdString(original_path)).completeBaseName()) + .append(extension); + QFileInfo dst_info = QFileInfo(dst_path); + if (dst_info.exists()) + { + ModalMessageBox confirm_replace(this); + confirm_replace.setIcon(QMessageBox::Warning); + confirm_replace.setWindowTitle(tr("Confirm")); + confirm_replace.setText(tr("The file %1 already exists.\n" + "Do you wish to replace it?") + .arg(dst_info.fileName())); + confirm_replace.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + if (confirm_replace.exec() == QMessageBox::No) + continue; + } + } + + ParallelProgressDialog progress_dialog(tr("Converting..."), tr("Abort"), 0, 100, this); + progress_dialog.GetRaw()->setWindowModality(Qt::WindowModal); + progress_dialog.GetRaw()->setWindowTitle(tr("Progress")); + + if (m_files.size() > 1) + { + progress_dialog.GetRaw()->setLabelText( + tr("Converting...") + QLatin1Char{'\n'} + + QFileInfo(QString::fromStdString(original_path)).fileName()); + } + + std::future good; + + if (format == DiscIO::BlobType::PLAIN) + { + good = std::async(std::launch::async, [&] { + const bool good = DiscIO::ConvertToPlain(original_path, dst_path.toStdString(), &CompressCB, + &progress_dialog); + progress_dialog.Reset(); + return good; + }); + } + else if (format == DiscIO::BlobType::GCZ) + { + good = std::async(std::launch::async, [&] { + const bool good = + DiscIO::ConvertToGCZ(original_path, dst_path.toStdString(), + file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, 16384, + &CompressCB, &progress_dialog); + progress_dialog.Reset(); + return good; + }); + } + + progress_dialog.GetRaw()->exec(); + if (!good.get()) + { + QErrorMessage(this).showMessage(tr("Dolphin failed to complete the requested action.")); + return; + } + } + + ModalMessageBox::information(this, tr("Success"), + tr("Successfully converted %n image(s).", "", m_files.size())); + + close(); +} diff --git a/Source/Core/DolphinQt/ConvertDialog.h b/Source/Core/DolphinQt/ConvertDialog.h new file mode 100644 index 0000000000..17aeb8f3e7 --- /dev/null +++ b/Source/Core/DolphinQt/ConvertDialog.h @@ -0,0 +1,37 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include +#include + +#include "DiscIO/Blob.h" + +class QComboBox; + +namespace UICommon +{ +class GameFile; +} + +class ConvertDialog final : public QDialog +{ + Q_OBJECT + +public: + explicit ConvertDialog(QList> files, + QWidget* parent = nullptr); + +private slots: + void Convert(); + +private: + void AddToFormatComboBox(const QString& name, DiscIO::BlobType format); + + QComboBox* m_format; + QList> m_files; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 1763c06300..3e9b862ddf 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -131,6 +131,7 @@ + @@ -211,6 +212,7 @@ + @@ -372,6 +374,7 @@ + diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index e4bf1c47bf..9178980106 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -40,6 +40,7 @@ #include "DiscIO/Enums.h" #include "DolphinQt/Config/PropertiesDialog.h" +#include "DolphinQt/ConvertDialog.h" #include "DolphinQt/GameList/GameListModel.h" #include "DolphinQt/GameList/GridProxyModel.h" #include "DolphinQt/GameList/ListProxyModel.h" @@ -53,8 +54,6 @@ #include "UICommon/GameFile.h" -static bool CompressCB(const std::string&, float, void*); - GameList::GameList(QWidget* parent) : QStackedWidget(parent) { m_model = Settings::Instance().GetGameListModel(); @@ -257,35 +256,16 @@ void GameList::ShowContextMenu(const QPoint&) if (HasMultipleSelected()) { - bool wii_saves = true; - bool compress = false; - bool decompress = false; - - for (const auto& game : GetSelectedGames()) + if (std::all_of(GetSelectedGames().begin(), GetSelectedGames().end(), [](const auto& game) { + return DiscIO::IsDisc(game->GetPlatform()) && game->IsVolumeSizeAccurate(); + })) { - DiscIO::Platform platform = game->GetPlatform(); - - if (platform == DiscIO::Platform::GameCubeDisc || platform == DiscIO::Platform::WiiDisc) - { - const auto blob_type = game->GetBlobType(); - if (blob_type == DiscIO::BlobType::GCZ) - decompress = true; - else if (blob_type == DiscIO::BlobType::PLAIN) - compress = true; - } - - if (platform != DiscIO::Platform::WiiWAD && platform != DiscIO::Platform::WiiDisc) - wii_saves = false; + menu->addAction(tr("Convert Selected Files..."), this, &GameList::ConvertFile); + menu->addSeparator(); } - if (compress) - menu->addAction(tr("Compress Selected ISOs..."), this, [this] { CompressISO(false); }); - if (decompress) - menu->addAction(tr("Decompress Selected ISOs..."), this, [this] { CompressISO(true); }); - if (compress || decompress) - menu->addSeparator(); - - if (wii_saves) + if (std::all_of(GetSelectedGames().begin(), GetSelectedGames().end(), + [](const auto& game) { return DiscIO::IsWii(game->GetPlatform()); })) { menu->addAction(tr("Export Wii Saves"), this, &GameList::ExportWiiSave); menu->addSeparator(); @@ -306,15 +286,13 @@ void GameList::ShowContextMenu(const QPoint&) menu->addSeparator(); } - if (platform == DiscIO::Platform::GameCubeDisc || platform == DiscIO::Platform::WiiDisc) + if (DiscIO::IsDisc(platform)) { menu->addAction(tr("Set as &Default ISO"), this, &GameList::SetDefaultISO); const auto blob_type = game->GetBlobType(); - if (blob_type == DiscIO::BlobType::GCZ) - menu->addAction(tr("Decompress ISO..."), this, [this] { CompressISO(true); }); - else if (blob_type == DiscIO::BlobType::PLAIN) - menu->addAction(tr("Compress ISO..."), this, [this] { CompressISO(false); }); + if (game->IsVolumeSizeAccurate()) + menu->addAction(tr("Convert File..."), this, &GameList::ConvertFile); QAction* change_disc = menu->addAction(tr("Change &Disc"), this, &GameList::ChangeDisc); @@ -481,157 +459,14 @@ void GameList::OpenWiki() QDesktopServices::openUrl(QUrl(url)); } -void GameList::CompressISO(bool decompress) +void GameList::ConvertFile() { - auto files = GetSelectedGames(); - const auto game = GetSelectedGame(); - - if (files.empty() || !game) + auto games = GetSelectedGames(); + if (games.empty()) return; - bool wii_warning_given = false; - for (QMutableListIterator> it(files); it.hasNext();) - { - auto file = it.next(); - - if ((file->GetPlatform() != DiscIO::Platform::GameCubeDisc && - file->GetPlatform() != DiscIO::Platform::WiiDisc) || - (decompress && file->GetBlobType() != DiscIO::BlobType::GCZ) || - (!decompress && file->GetBlobType() != DiscIO::BlobType::PLAIN)) - { - it.remove(); - continue; - } - - if (!wii_warning_given && !decompress && file->GetPlatform() == DiscIO::Platform::WiiDisc) - { - ModalMessageBox wii_warning(this); - wii_warning.setIcon(QMessageBox::Warning); - wii_warning.setWindowTitle(tr("Confirm")); - wii_warning.setText(tr("Are you sure?")); - wii_warning.setInformativeText(tr( - "Compressing a Wii disc image will irreversibly change the compressed copy by removing " - "padding data. Your disc image will still work. Continue?")); - wii_warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - - if (wii_warning.exec() == QMessageBox::No) - return; - - wii_warning_given = true; - } - } - - QString dst_dir; - QString dst_path; - - if (files.size() > 1) - { - dst_dir = QFileDialog::getExistingDirectory( - this, - decompress ? tr("Select where you want to save the decompressed images") : - tr("Select where you want to save the compressed images"), - QFileInfo(QString::fromStdString(game->GetFilePath())).dir().absolutePath()); - - if (dst_dir.isEmpty()) - return; - } - else - { - dst_path = QFileDialog::getSaveFileName( - this, - decompress ? tr("Select where you want to save the decompressed image") : - tr("Select where you want to save the compressed image"), - QFileInfo(QString::fromStdString(game->GetFilePath())) - .dir() - .absoluteFilePath( - QFileInfo(QString::fromStdString(files[0]->GetFilePath())).completeBaseName()) - .append(decompress ? QStringLiteral(".gcm") : QStringLiteral(".gcz")), - decompress ? tr("Uncompressed GC/Wii images (*.iso *.gcm)") : - tr("Compressed GC/Wii images (*.gcz)")); - - if (dst_path.isEmpty()) - return; - } - - for (const auto& file : files) - { - const auto original_path = file->GetFilePath(); - if (files.size() > 1) - { - dst_path = - QDir(dst_dir) - .absoluteFilePath(QFileInfo(QString::fromStdString(original_path)).completeBaseName()) - .append(decompress ? QStringLiteral(".gcm") : QStringLiteral(".gcz")); - QFileInfo dst_info = QFileInfo(dst_path); - if (dst_info.exists()) - { - ModalMessageBox confirm_replace(this); - confirm_replace.setIcon(QMessageBox::Warning); - confirm_replace.setWindowTitle(tr("Confirm")); - confirm_replace.setText(tr("The file %1 already exists.\n" - "Do you wish to replace it?") - .arg(dst_info.fileName())); - confirm_replace.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - - if (confirm_replace.exec() == QMessageBox::No) - continue; - } - } - - ParallelProgressDialog progress_dialog( - decompress ? tr("Decompressing...") : tr("Compressing..."), tr("Abort"), 0, 100, this); - progress_dialog.GetRaw()->setWindowModality(Qt::WindowModal); - progress_dialog.GetRaw()->setWindowTitle(tr("Progress")); - - std::future good; - - if (decompress) - { - if (files.size() > 1) - { - progress_dialog.GetRaw()->setLabelText( - tr("Decompressing...") + QLatin1Char{'\n'} + - QFileInfo(QString::fromStdString(original_path)).fileName()); - } - - good = std::async(std::launch::async, [&] { - const bool good = DiscIO::ConvertToPlain(original_path, dst_path.toStdString(), &CompressCB, - &progress_dialog); - progress_dialog.Reset(); - return good; - }); - } - else - { - if (files.size() > 1) - { - progress_dialog.GetRaw()->setLabelText( - tr("Compressing...") + QLatin1Char{'\n'} + - QFileInfo(QString::fromStdString(original_path)).fileName()); - } - - good = std::async(std::launch::async, [&] { - const bool good = - DiscIO::ConvertToGCZ(original_path, dst_path.toStdString(), - file->GetPlatform() == DiscIO::Platform::WiiDisc ? 1 : 0, 16384, - &CompressCB, &progress_dialog); - progress_dialog.Reset(); - return good; - }); - } - - progress_dialog.GetRaw()->exec(); - if (!good.get()) - { - QErrorMessage(this).showMessage(tr("Dolphin failed to complete the requested action.")); - return; - } - } - - ModalMessageBox::information(this, tr("Success"), - decompress ? - tr("Successfully decompressed %n image(s).", "", files.size()) : - tr("Successfully compressed %n image(s).", "", files.size())); + ConvertDialog dialog{std::move(games), this}; + dialog.exec(); } void GameList::InstallWAD() @@ -949,17 +784,6 @@ void GameList::OnGameListVisibilityChanged() m_grid_proxy->invalidate(); } -static bool CompressCB(const std::string& text, float percent, void* ptr) -{ - if (ptr == nullptr) - return false; - - auto* progress_dialog = static_cast(ptr); - - progress_dialog->SetValue(percent * 100); - return !progress_dialog->WasCanceled(); -} - void GameList::OnSectionResized(int index, int, int) { auto* hor_header = m_list->horizontalHeader(); diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index 596c92f57e..6587653ee4 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -64,7 +64,7 @@ private: void InstallWAD(); void UninstallWAD(); void ExportWiiSave(); - void CompressISO(bool decompress); + void ConvertFile(); void ChangeDisc(); void NewTag(); void DeleteTag(); diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index cc64d9d3e9..139dcce688 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -116,6 +116,7 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path)) m_blob_type = volume->GetBlobType(); m_file_size = volume->GetRawSize(); m_volume_size = volume->GetSize(); + m_volume_size_is_accurate = volume->IsSizeAccurate(); m_internal_name = volume->GetInternalName(); m_game_id = volume->GetGameID(); @@ -136,6 +137,7 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path)) { m_valid = true; m_file_size = m_volume_size = File::GetSize(m_file_path); + m_volume_size_is_accurate = true; m_platform = DiscIO::Platform::ELFOrDOL; m_blob_type = DiscIO::BlobType::DIRECTORY; } @@ -296,6 +298,7 @@ void GameFile::DoState(PointerWrap& p) p.Do(m_file_size); p.Do(m_volume_size); + p.Do(m_volume_size_is_accurate); p.Do(m_short_names); p.Do(m_long_names); diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index 87513118a9..53401e8756 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -89,6 +89,7 @@ public: const std::string& GetApploaderDate() const { return m_apploader_date; } u64 GetFileSize() const { return m_file_size; } u64 GetVolumeSize() const { return m_volume_size; } + bool IsVolumeSizeAccurate() const { return m_volume_size_is_accurate; } const GameBanner& GetBannerImage() const; const GameCover& GetCoverImage() const; void DoState(PointerWrap& p); @@ -124,6 +125,7 @@ private: u64 m_file_size{}; u64 m_volume_size{}; + bool m_volume_size_is_accurate{}; std::map m_short_names; std::map m_long_names; diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 07cbf726c4..9b2729407e 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -27,7 +27,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 16; // Last changed in PR 8313 +static constexpr u32 CACHE_REVISION = 17; // Last changed in PR 8738 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan)