diff --git a/Source/Core/Core/WiiUtils.cpp b/Source/Core/Core/WiiUtils.cpp index 6940123fa3..c58cc9d65c 100644 --- a/Source/Core/Core/WiiUtils.cpp +++ b/Source/Core/Core/WiiUtils.cpp @@ -5,7 +5,10 @@ #include "Core/WiiUtils.h" #include +#include #include +#include +#include #include #include #include @@ -32,15 +35,19 @@ #include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "Core/IOS/IOS.h" +#include "DiscIO/DiscExtractor.h" #include "DiscIO/Enums.h" +#include "DiscIO/Filesystem.h" #include "DiscIO/NANDContentLoader.h" +#include "DiscIO/Volume.h" +#include "DiscIO/VolumeFileBlobReader.h" +#include "DiscIO/VolumeWii.h" #include "DiscIO/WiiWad.h" namespace WiiUtils { -bool InstallWAD(const std::string& wad_path) +bool InstallWAD(IOS::HLE::Kernel& ios, const DiscIO::WiiWAD& wad) { - const DiscIO::WiiWAD wad{wad_path}; if (!wad.IsValid()) { PanicAlertT("WAD installation failed: The selected file is not a valid WAD."); @@ -48,7 +55,6 @@ bool InstallWAD(const std::string& wad_path) } const auto tmd = wad.GetTMD(); - IOS::HLE::Kernel ios; const auto es = ios.GetES(); IOS::HLE::Device::ES::Context context; @@ -99,10 +105,19 @@ bool InstallWAD(const std::string& wad_path) return false; } - DiscIO::NANDContentManager::Access().ClearCache(); return true; } +bool InstallWAD(const std::string& wad_path) +{ + IOS::HLE::Kernel ios; + const DiscIO::WiiWAD wad{wad_path}; + const bool result = InstallWAD(ios, wad); + + DiscIO::NANDContentManager::Access().ClearCache(); + return result; +} + // Common functionality for system updaters. class SystemUpdater { @@ -118,7 +133,6 @@ protected: std::string GetDeviceRegion(); std::string GetDeviceId(); - bool ShouldInstallTitle(const TitleInfo& title); IOS::HLE::Kernel m_ios; }; @@ -149,14 +163,6 @@ std::string SystemUpdater::GetDeviceId() return StringFromFormat("%" PRIu64, (u64(1) << 32) | ios_device_id); } -bool SystemUpdater::ShouldInstallTitle(const TitleInfo& title) -{ - const auto es = m_ios.GetES(); - const auto installed_tmd = es->FindInstalledTMD(title.id); - return !(installed_tmd.IsValid() && installed_tmd.GetTitleVersion() >= title.version && - es->GetStoredContentsFromTMD(installed_tmd).size() == installed_tmd.GetNumContents()); -} - class OnlineSystemUpdater final : public SystemUpdater { public: @@ -172,6 +178,7 @@ private: Response GetSystemTitles(); Response ParseTitlesResponse(const std::vector& response) const; + bool ShouldInstallTitle(const TitleInfo& title); UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title, std::unordered_set* updated_titles); @@ -241,6 +248,14 @@ OnlineSystemUpdater::ParseTitlesResponse(const std::vector& response) const return info; } +bool OnlineSystemUpdater::ShouldInstallTitle(const TitleInfo& title) +{ + const auto es = m_ios.GetES(); + const auto installed_tmd = es->FindInstalledTMD(title.id); + return !(installed_tmd.IsValid() && installed_tmd.GetTitleVersion() >= title.version && + es->GetStoredContentsFromTMD(installed_tmd).size() == installed_tmd.GetNumContents()); +} + constexpr const char* GET_SYSTEM_TITLES_REQUEST_PAYLOAD = R"( > OnlineSystemUpdater::DownloadContent(const std::s return m_http.Get(url); } +class DiscSystemUpdater final : public SystemUpdater +{ +public: + DiscSystemUpdater(UpdateCallback update_callback, const std::string& image_path) + : m_update_callback{std::move(update_callback)}, + m_volume{DiscIO::CreateVolumeFromFilename(image_path)} + { + } + UpdateResult DoDiscUpdate(); + +private: +#pragma pack(push, 8) + struct ManifestHeader + { + char timestamp[0x10]; // YYYY/MM/DD + // There is a u32 in newer info files to indicate the number of entries, + // but it's not used in older files, and it's not always at the same offset. + // Too unreliable to use it. + u32 padding[4]; + }; + static_assert(sizeof(ManifestHeader) == 32, "Wrong size"); + + struct Entry + { + u32 type; + u32 attribute; + u32 unknown1; + u32 unknown2; + char path[0x40]; + u64 title_id; + u16 title_version; + char name[0x40]; + char info[0x40]; + u8 unused[0x120]; + }; + static_assert(sizeof(Entry) == 512, "Wrong size"); +#pragma pack(pop) + + UpdateResult UpdateFromManifest(const std::string& manifest_name); + UpdateResult ProcessEntry(u32 type, std::bitset<32> attrs, const TitleInfo& title, + const std::string& path); + + UpdateCallback m_update_callback; + std::unique_ptr m_volume; + std::unique_ptr m_disc_fs; +}; + +UpdateResult DiscSystemUpdater::DoDiscUpdate() +{ + if (!m_volume) + return UpdateResult::DiscReadFailed; + + // Do not allow mismatched regions, because installing an update will automatically change + // the Wii's region and may result in semi/full system menu bricks. + const IOS::ES::TMDReader system_menu_tmd = m_ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU); + if (system_menu_tmd.IsValid() && m_volume->GetRegion() != system_menu_tmd.GetRegion()) + return UpdateResult::RegionMismatch; + + const auto partitions = m_volume->GetPartitions(); + const auto update_partition = + std::find_if(partitions.cbegin(), partitions.cend(), [&](const DiscIO::Partition& partition) { + return m_volume->GetPartitionType(partition) == 1u; + }); + + if (update_partition == partitions.cend()) + { + ERROR_LOG(CORE, "Could not find any update partition"); + return UpdateResult::MissingUpdatePartition; + } + + m_disc_fs = DiscIO::CreateFileSystem(m_volume.get(), *update_partition); + if (!m_disc_fs || !m_disc_fs->IsValid()) + return UpdateResult::DiscReadFailed; + + return UpdateFromManifest("__update.inf"); +} + +UpdateResult DiscSystemUpdater::UpdateFromManifest(const std::string& manifest_name) +{ + const std::unique_ptr update_manifest = m_disc_fs->FindFileInfo(manifest_name); + if (!update_manifest || + (update_manifest->GetSize() - sizeof(ManifestHeader)) % sizeof(Entry) != 0) + { + ERROR_LOG(CORE, "Invalid or missing update manifest"); + return UpdateResult::DiscReadFailed; + } + + const u32 num_entries = (update_manifest->GetSize() - sizeof(ManifestHeader)) / sizeof(Entry); + if (num_entries > 200) + return UpdateResult::DiscReadFailed; + + std::vector entry(sizeof(Entry)); + size_t updates_installed = 0; + for (u32 i = 0; i < num_entries; ++i) + { + const u32 offset = sizeof(ManifestHeader) + sizeof(Entry) * i; + if (entry.size() != DiscIO::ReadFile(*m_volume, m_disc_fs->GetPartition(), + update_manifest.get(), entry.data(), entry.size(), offset)) + { + ERROR_LOG(CORE, "Failed to read update information from update manifest"); + return UpdateResult::DiscReadFailed; + } + + const u32 type = Common::swap32(entry.data() + offsetof(Entry, type)); + const std::bitset<32> attrs = Common::swap32(entry.data() + offsetof(Entry, attribute)); + const u64 title_id = Common::swap64(entry.data() + offsetof(Entry, title_id)); + const u16 title_version = Common::swap16(entry.data() + offsetof(Entry, title_version)); + const char* path_pointer = reinterpret_cast(entry.data() + offsetof(Entry, path)); + const std::string path{path_pointer, strnlen(path_pointer, sizeof(Entry::path))}; + + if (!m_update_callback(i, num_entries, title_id)) + return UpdateResult::Cancelled; + + const UpdateResult res = ProcessEntry(type, attrs, {title_id, title_version}, path); + if (res != UpdateResult::Succeeded && res != UpdateResult::AlreadyUpToDate) + { + ERROR_LOG(CORE, "Failed to update %016" PRIx64 " -- aborting update", title_id); + return res; + } + + if (res == UpdateResult::Succeeded) + ++updates_installed; + } + return updates_installed == 0 ? UpdateResult::AlreadyUpToDate : UpdateResult::Succeeded; +} + +UpdateResult DiscSystemUpdater::ProcessEntry(u32 type, std::bitset<32> attrs, + const TitleInfo& title, const std::string& path) +{ + // Skip any unknown type and boot2 updates (for now). + if (type != 2 && type != 3 && type != 6 && type != 7) + return UpdateResult::AlreadyUpToDate; + + const IOS::ES::TMDReader tmd = m_ios.GetES()->FindInstalledTMD(title.id); + const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(title.id); + + // Optional titles can be skipped if the ticket is present, even when the title isn't installed. + if (attrs.test(16) && ticket.IsValid()) + return UpdateResult::AlreadyUpToDate; + + // Otherwise, the title is only skipped if it is installed, its ticket is imported, + // and the installed version is new enough. No further checks unlike the online updater. + if (tmd.IsValid() && tmd.GetTitleVersion() >= title.version) + return UpdateResult::AlreadyUpToDate; + + // Import the WAD. + const std::unique_ptr wad_file = m_disc_fs->FindFileInfo(path); + if (!wad_file) + { + ERROR_LOG(CORE, "Failed to get info for %s", path.c_str()); + return UpdateResult::DiscReadFailed; + } + + const DiscIO::WiiWAD wad{DiscIO::VolumeFileBlobReader::Create(*m_volume, *m_disc_fs, path)}; + return InstallWAD(m_ios, wad) ? UpdateResult::Succeeded : UpdateResult::ImportFailed; +} + UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region) { OnlineSystemUpdater updater{std::move(update_callback), region}; @@ -500,4 +672,12 @@ UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& r DiscIO::NANDContentManager::Access().ClearCache(); return result; } + +UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path) +{ + DiscSystemUpdater updater{std::move(update_callback), image_path}; + const UpdateResult result = updater.DoDiscUpdate(); + DiscIO::NANDContentManager::Access().ClearCache(); + return result; +} } diff --git a/Source/Core/Core/WiiUtils.h b/Source/Core/Core/WiiUtils.h index 3d0eff6b9a..49e8175bc2 100644 --- a/Source/Core/Core/WiiUtils.h +++ b/Source/Core/Core/WiiUtils.h @@ -21,10 +21,18 @@ enum class UpdateResult Succeeded, AlreadyUpToDate, + // Current region does not match disc region. + RegionMismatch, + // Missing update partition on disc. + MissingUpdatePartition, + // Missing or invalid files on disc. + DiscReadFailed, + // NUS errors and failures. ServerFailed, // General download failures. DownloadFailed, + // Import failures. ImportFailed, // Update was cancelled. @@ -37,4 +45,7 @@ using UpdateCallback = std::function + @@ -73,6 +74,7 @@ + @@ -96,4 +98,4 @@ - \ No newline at end of file + diff --git a/Source/Core/DiscIO/DiscIO.vcxproj.filters b/Source/Core/DiscIO/DiscIO.vcxproj.filters index 3cebc18705..89cfed8abb 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj.filters +++ b/Source/Core/DiscIO/DiscIO.vcxproj.filters @@ -60,6 +60,9 @@ Volume\Blob + + Volume + Volume @@ -125,6 +128,9 @@ Volume\Blob + + Volume + Volume @@ -147,4 +153,4 @@ - \ No newline at end of file + diff --git a/Source/Core/DiscIO/VolumeFileBlobReader.cpp b/Source/Core/DiscIO/VolumeFileBlobReader.cpp new file mode 100644 index 0000000000..5b243acc29 --- /dev/null +++ b/Source/Core/DiscIO/VolumeFileBlobReader.cpp @@ -0,0 +1,51 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DiscIO/VolumeFileBlobReader.h" + +#include "DiscIO/Filesystem.h" +#include "DiscIO/Volume.h" + +namespace DiscIO +{ +std::unique_ptr VolumeFileBlobReader::Create(const Volume& volume, + const FileSystem& file_system, + const std::string& file_path) +{ + if (!file_system.IsValid()) + return nullptr; + + std::unique_ptr file_info = file_system.FindFileInfo(file_path); + if (!file_info || file_info->IsDirectory()) + return nullptr; + + return std::unique_ptr{ + new VolumeFileBlobReader(volume, file_system, std::move(file_info))}; +} + +VolumeFileBlobReader::VolumeFileBlobReader(const Volume& volume, const FileSystem& file_system, + std::unique_ptr file_info) + : m_volume(volume), m_file_system(file_system), m_file_info(std::move(file_info)) +{ +} + +u64 VolumeFileBlobReader::GetDataSize() const +{ + return m_file_info->GetSize(); +} + +u64 VolumeFileBlobReader::GetRawSize() const +{ + return GetDataSize(); +} + +bool VolumeFileBlobReader::Read(u64 offset, u64 length, u8* out_ptr) +{ + if (offset + length > m_file_info->GetSize()) + return false; + + return m_volume.Read(m_file_info->GetOffset() + offset, length, out_ptr, + m_file_system.GetPartition()); +} +} // namespace diff --git a/Source/Core/DiscIO/VolumeFileBlobReader.h b/Source/Core/DiscIO/VolumeFileBlobReader.h new file mode 100644 index 0000000000..e915e03560 --- /dev/null +++ b/Source/Core/DiscIO/VolumeFileBlobReader.h @@ -0,0 +1,38 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "DiscIO/Blob.h" + +namespace DiscIO +{ +class FileInfo; +class FileSystem; +class Volume; + +class VolumeFileBlobReader final : public BlobReader +{ +public: + static std::unique_ptr + Create(const Volume& volume, const FileSystem& file_system, const std::string& file_path); + + BlobType GetBlobType() const override { return BlobType::PLAIN; } + u64 GetDataSize() const override; + u64 GetRawSize() const override; + bool Read(u64 offset, u64 length, u8* out_ptr) override; + +private: + VolumeFileBlobReader(const Volume& volume, const FileSystem& file_system, + std::unique_ptr file_info); + + const Volume& m_volume; + const FileSystem& m_file_system; + std::unique_ptr m_file_info; +}; +} // namespace diff --git a/Source/Core/DiscIO/WiiWad.cpp b/Source/Core/DiscIO/WiiWad.cpp index 1b04f3e68d..8d6272d3a8 100644 --- a/Source/Core/DiscIO/WiiWad.cpp +++ b/Source/Core/DiscIO/WiiWad.cpp @@ -44,21 +44,18 @@ bool IsWiiWAD(BlobReader& reader) } } // Anonymous namespace -WiiWAD::WiiWAD(const std::string& name) : m_reader(CreateBlobReader(name)) -{ - if (m_reader == nullptr) - { - m_valid = false; - return; - } - - m_valid = ParseWAD(); -} - -WiiWAD::~WiiWAD() +WiiWAD::WiiWAD(const std::string& name) : WiiWAD(CreateBlobReader(name)) { } +WiiWAD::WiiWAD(std::unique_ptr blob_reader) : m_reader(std::move(blob_reader)) +{ + if (m_reader) + m_valid = ParseWAD(); +} + +WiiWAD::~WiiWAD() = default; + bool WiiWAD::ParseWAD() { if (!IsWiiWAD(*m_reader)) diff --git a/Source/Core/DiscIO/WiiWad.h b/Source/Core/DiscIO/WiiWad.h index 1c29d77ff4..6f3f4a97bc 100644 --- a/Source/Core/DiscIO/WiiWad.h +++ b/Source/Core/DiscIO/WiiWad.h @@ -19,6 +19,7 @@ class WiiWAD { public: explicit WiiWAD(const std::string& name); + explicit WiiWAD(std::unique_ptr blob_reader); ~WiiWAD(); bool IsValid() const { return m_valid; } @@ -32,7 +33,7 @@ public: private: bool ParseWAD(); - bool m_valid; + bool m_valid = false; std::unique_ptr m_reader; diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp index defeedbbca..0c3915dbcc 100644 --- a/Source/Core/DolphinQt2/GameList/GameList.cpp +++ b/Source/Core/DolphinQt2/GameList/GameList.cpp @@ -29,6 +29,7 @@ #include "DolphinQt2/GameList/ListProxyModel.h" #include "DolphinQt2/QtUtils/DoubleClickEventFilter.h" #include "DolphinQt2/Settings.h" +#include "DolphinQt2/WiiUpdate.h" static bool CompressCB(const std::string&, float, void*); @@ -164,6 +165,15 @@ void GameList::ShowContextMenu(const QPoint&) menu->addSeparator(); } + + if (platform == DiscIO::Platform::WII_DISC) + { + menu->addAction(tr("Perform System Update"), [this] { + WiiUpdate::PerformDiscUpdate(GetSelectedGame().toStdString(), this); + }); + menu->setEnabled(!Core::IsRunning() || !SConfig::GetInstance().bWii); + } + if (platform == DiscIO::Platform::WII_WAD) { QAction* wad_install_action = new QAction(tr("Install to the NAND"), menu); diff --git a/Source/Core/DolphinQt2/WiiUpdate.cpp b/Source/Core/DolphinQt2/WiiUpdate.cpp index 86200b21d1..397765f4c9 100644 --- a/Source/Core/DolphinQt2/WiiUpdate.cpp +++ b/Source/Core/DolphinQt2/WiiUpdate.cpp @@ -22,66 +22,9 @@ namespace WiiUpdate { -void PerformOnlineUpdate(const std::string& region, QWidget* parent) +static void ShowResult(QWidget* parent, WiiUtils::UpdateResult result) { - const int confirm = QMessageBox::question( - parent, QObject::tr("Confirm"), - QObject::tr("Connect to the Internet and perform an online system update?")); - if (confirm != QMessageBox::Yes) - return; - - // Do not allow the user to close the dialog. Instead, wait until the update is finished - // or cancelled. - class UpdateProgressDialog final : public QProgressDialog - { - public: - using QProgressDialog::QProgressDialog; - - protected: - void reject() override {} - }; - - UpdateProgressDialog dialog{parent}; - dialog.setLabelText(QObject::tr("Preparing to update...\nThis can take a while.")); - dialog.setWindowTitle(QObject::tr("Updating")); - dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - // QProgressDialog doesn't set its minimum size correctly. - dialog.setMinimumSize(360, 150); - - // QProgressDialog doesn't allow us to disable the cancel button when it's pressed, - // so we have to pass it our own push button. Note: the dialog takes ownership of it. - auto* cancel_button = new QPushButton(QObject::tr("&Cancel"), parent); - dialog.setCancelButton(cancel_button); - Common::Flag was_cancelled; - QObject::disconnect(&dialog, &QProgressDialog::canceled, &dialog, &QProgressDialog::cancel); - QObject::connect(&dialog, &QProgressDialog::canceled, [&] { - dialog.setLabelText(QObject::tr("Finishing the update...\nThis can take a while.")); - cancel_button->setEnabled(false); - was_cancelled.Set(); - }); - - std::future result = std::async(std::launch::async, [&] { - const WiiUtils::UpdateResult res = WiiUtils::DoOnlineUpdate( - [&](size_t processed, size_t total, u64 title_id) { - QueueOnObject(&dialog, [&dialog, &was_cancelled, processed, total, title_id]() { - if (was_cancelled.IsSet()) - return; - - dialog.setRange(0, static_cast(total)); - dialog.setValue(static_cast(processed)); - dialog.setLabelText(QObject::tr("Updating title %1...\nThis can take a while.") - .arg(title_id, 16, 16, QLatin1Char('0'))); - }); - return !was_cancelled.IsSet(); - }, - region); - QueueOnObject(&dialog, [&dialog] { dialog.close(); }); - return res; - }); - - dialog.exec(); - - switch (result.get()) + switch (result) { case WiiUtils::UpdateResult::Succeeded: QMessageBox::information(parent, QObject::tr("Update completed"), @@ -114,6 +57,92 @@ void PerformOnlineUpdate(const std::string& region, QWidget* parent) QObject::tr("The update has been cancelled. It is strongly recommended to " "finish it in order to avoid inconsistent system software versions.")); break; + case WiiUtils::UpdateResult::RegionMismatch: + QMessageBox::critical(parent, QObject::tr("Update failed"), + QObject::tr("The game's region does not match your console's. " + "To avoid issues with the system menu, it is not possible " + "to update the emulated console using this disc.")); + break; + case WiiUtils::UpdateResult::MissingUpdatePartition: + case WiiUtils::UpdateResult::DiscReadFailed: + QMessageBox::critical(parent, QObject::tr("Update failed"), + QObject::tr("The game disc does not contain any usable " + "update information.")); + break; } } + +template +static WiiUtils::UpdateResult ShowProgress(QWidget* parent, Callable function, Args&&... args) +{ + // Do not allow the user to close the dialog. Instead, wait until the update is finished + // or cancelled. + class UpdateProgressDialog final : public QProgressDialog + { + public: + using QProgressDialog::QProgressDialog; + + protected: + void reject() override {} + }; + + UpdateProgressDialog dialog{parent}; + dialog.setLabelText(QObject::tr("Preparing to update...\nThis can take a while.")); + dialog.setWindowTitle(QObject::tr("Updating")); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + // QProgressDialog doesn't set its minimum size correctly. + dialog.setMinimumSize(360, 150); + + // QProgressDialog doesn't allow us to disable the cancel button when it's pressed, + // so we have to pass it our own push button. Note: the dialog takes ownership of it. + auto* cancel_button = new QPushButton(QObject::tr("&Cancel"), parent); + dialog.setCancelButton(cancel_button); + Common::Flag was_cancelled; + QObject::disconnect(&dialog, &QProgressDialog::canceled, nullptr, nullptr); + QObject::connect(&dialog, &QProgressDialog::canceled, [&] { + dialog.setLabelText(QObject::tr("Finishing the update...\nThis can take a while.")); + cancel_button->setEnabled(false); + was_cancelled.Set(); + }); + + std::future result = std::async(std::launch::async, [&] { + const WiiUtils::UpdateResult res = function( + [&](size_t processed, size_t total, u64 title_id) { + QueueOnObject(&dialog, [&dialog, &was_cancelled, processed, total, title_id] { + if (was_cancelled.IsSet()) + return; + + dialog.setRange(0, static_cast(total)); + dialog.setValue(static_cast(processed)); + dialog.setLabelText(QObject::tr("Updating title %1...\nThis can take a while.") + .arg(title_id, 16, 16, QLatin1Char('0'))); + }); + return !was_cancelled.IsSet(); + }, + std::forward(args)...); + QueueOnObject(&dialog, [&dialog] { dialog.done(0); }); + return res; + }); + + dialog.exec(); + return result.get(); +} + +void PerformOnlineUpdate(const std::string& region, QWidget* parent) +{ + const int confirm = QMessageBox::question( + parent, QObject::tr("Confirm"), + QObject::tr("Connect to the Internet and perform an online system update?")); + if (confirm != QMessageBox::Yes) + return; + + const WiiUtils::UpdateResult result = ShowProgress(parent, WiiUtils::DoOnlineUpdate, region); + ShowResult(parent, result); +} + +void PerformDiscUpdate(const std::string& file_path, QWidget* parent) +{ + const WiiUtils::UpdateResult result = ShowProgress(parent, WiiUtils::DoDiscUpdate, file_path); + ShowResult(parent, result); +} } // namespace WiiUpdate diff --git a/Source/Core/DolphinQt2/WiiUpdate.h b/Source/Core/DolphinQt2/WiiUpdate.h index 19b7b8f463..cfeb6a7402 100644 --- a/Source/Core/DolphinQt2/WiiUpdate.h +++ b/Source/Core/DolphinQt2/WiiUpdate.h @@ -11,4 +11,5 @@ class QWidget; namespace WiiUpdate { void PerformOnlineUpdate(const std::string& region, QWidget* parent = nullptr); +void PerformDiscUpdate(const std::string& file_path, QWidget* parent = nullptr); } // namespace WiiUpdate diff --git a/Source/Core/DolphinWX/Frame.cpp b/Source/Core/DolphinWX/Frame.cpp index 5977fd3ba5..4f350f3978 100644 --- a/Source/Core/DolphinWX/Frame.cpp +++ b/Source/Core/DolphinWX/Frame.cpp @@ -245,6 +245,7 @@ BEGIN_EVENT_TABLE(CFrame, CRenderFrame) EVT_MENU_RANGE(IDM_FLOAT_LOG_WINDOW, IDM_FLOAT_CODE_WINDOW, CFrame::OnFloatWindow) // Game list context menu +EVT_MENU(IDM_LIST_PERFORM_DISC_UPDATE, CFrame::OnPerformDiscWiiUpdate) EVT_MENU(IDM_LIST_INSTALL_WAD, CFrame::OnInstallWAD) EVT_MENU(IDM_LIST_UNINSTALL_WAD, CFrame::OnUninstallWAD) diff --git a/Source/Core/DolphinWX/Frame.h b/Source/Core/DolphinWX/Frame.h index 76d8c7df63..cb5364155d 100644 --- a/Source/Core/DolphinWX/Frame.h +++ b/Source/Core/DolphinWX/Frame.h @@ -349,6 +349,7 @@ private: void OnImportBootMiiBackup(wxCommandEvent& event); void OnExtractCertificates(wxCommandEvent& event); void OnPerformOnlineWiiUpdate(wxCommandEvent& event); + void OnPerformDiscWiiUpdate(wxCommandEvent& event); void OnFifoPlayer(wxCommandEvent& event); void OnConnectWiimote(wxCommandEvent& event); void GameListChanged(wxCommandEvent& event); diff --git a/Source/Core/DolphinWX/FrameTools.cpp b/Source/Core/DolphinWX/FrameTools.cpp index 787f5ae1c1..b2d8622ac9 100644 --- a/Source/Core/DolphinWX/FrameTools.cpp +++ b/Source/Core/DolphinWX/FrameTools.cpp @@ -1320,39 +1320,9 @@ static std::string GetUpdateRegionFromIdm(int idm) } } -void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event) +static void ShowUpdateResult(WiiUtils::UpdateResult result) { - int confirm = wxMessageBox(_("Connect to the Internet and perform an online system update?"), - _("System Update"), wxYES_NO, this); - if (confirm != wxYES) - return; - - wxProgressDialog dialog(_("Updating"), _("Preparing to update...\nThis can take a while."), 1, - this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH | wxPD_CAN_ABORT); - - const std::string region = GetUpdateRegionFromIdm(event.GetId()); - std::future result = std::async(std::launch::async, [&] { - const WiiUtils::UpdateResult res = WiiUtils::DoOnlineUpdate( - [&](size_t processed, size_t total, u64 title_id) { - Core::QueueHostJob( - [&dialog, processed, total, title_id] { - dialog.SetRange(total); - dialog.Update(processed, wxString::Format(_("Updating title %016" PRIx64 "...\n" - "This can take a while."), - title_id)); - dialog.Fit(); - }, - true); - return !dialog.WasCancelled(); - }, - region); - Core::QueueHostJob([&dialog] { dialog.EndModal(0); }, true); - return res; - }); - - dialog.ShowModal(); - - switch (result.get()) + switch (result) { case WiiUtils::UpdateResult::Succeeded: wxMessageBox(_("The emulated Wii console has been updated."), _("Update completed"), @@ -1384,8 +1354,73 @@ void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event) "finish it in order to avoid inconsistent system software versions."), _("Update cancelled"), wxOK | wxICON_WARNING); break; + case WiiUtils::UpdateResult::RegionMismatch: + wxMessageBox(_("The game's region does not match your console's. " + "To avoid issues with the system menu, it is not possible to update " + "the emulated console using this disc."), + _("Update failed"), wxOK | wxICON_ERROR); + break; + case WiiUtils::UpdateResult::MissingUpdatePartition: + case WiiUtils::UpdateResult::DiscReadFailed: + wxMessageBox(_("The game disc does not contain any usable update information."), + _("Update failed"), wxOK | wxICON_ERROR); + break; } +} +template +static WiiUtils::UpdateResult ShowUpdateProgress(CFrame* frame, Callable function, Args&&... args) +{ + wxProgressDialog dialog(_("Updating"), _("Preparing to update...\nThis can take a while."), 1, + frame, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH | wxPD_CAN_ABORT); + + std::future result = std::async(std::launch::async, [&] { + const WiiUtils::UpdateResult res = function( + [&](size_t processed, size_t total, u64 title_id) { + Core::QueueHostJob( + [&dialog, processed, total, title_id] { + dialog.SetRange(total); + dialog.Update(processed, wxString::Format(_("Updating title %016" PRIx64 "...\n" + "This can take a while."), + title_id)); + dialog.Fit(); + }, + true); + return !dialog.WasCancelled(); + }, + std::forward(args)...); + Core::QueueHostJob([&dialog] { dialog.EndModal(0); }, true); + return res; + }); + + dialog.ShowModal(); + return result.get(); +} + +void CFrame::OnPerformOnlineWiiUpdate(wxCommandEvent& event) +{ + int confirm = wxMessageBox(_("Connect to the Internet and perform an online system update?"), + _("System Update"), wxYES_NO, this); + if (confirm != wxYES) + return; + + const std::string region = GetUpdateRegionFromIdm(event.GetId()); + + const WiiUtils::UpdateResult result = ShowUpdateProgress(this, WiiUtils::DoOnlineUpdate, region); + ShowUpdateResult(result); + UpdateLoadWiiMenuItem(); +} + +void CFrame::OnPerformDiscWiiUpdate(wxCommandEvent&) +{ + const GameListItem* iso = m_game_list_ctrl->GetSelectedISO(); + if (!iso) + return; + + const std::string file_name = iso->GetFileName(); + + const WiiUtils::UpdateResult result = ShowUpdateProgress(this, WiiUtils::DoDiscUpdate, file_name); + ShowUpdateResult(result); UpdateLoadWiiMenuItem(); } diff --git a/Source/Core/DolphinWX/GameListCtrl.cpp b/Source/Core/DolphinWX/GameListCtrl.cpp index 4cd9f178f7..5d91ca7193 100644 --- a/Source/Core/DolphinWX/GameListCtrl.cpp +++ b/Source/Core/DolphinWX/GameListCtrl.cpp @@ -1173,6 +1173,13 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event) changeDiscItem->Enable(Core::IsRunning()); } + if (platform == DiscIO::Platform::WII_DISC) + { + auto* const perform_update_item = + popupMenu.Append(IDM_LIST_PERFORM_DISC_UPDATE, _("Perform System Update")); + perform_update_item->Enable(!Core::IsRunning() || !SConfig::GetInstance().bWii); + } + if (platform == DiscIO::Platform::WII_WAD) { auto* const install_wad_item = diff --git a/Source/Core/DolphinWX/Globals.h b/Source/Core/DolphinWX/Globals.h index d85d5add8e..b72cf2686b 100644 --- a/Source/Core/DolphinWX/Globals.h +++ b/Source/Core/DolphinWX/Globals.h @@ -100,6 +100,7 @@ enum IDM_GAME_WIKI, IDM_LOAD_WII_MENU, IDM_MENU_INSTALL_WAD, + IDM_LIST_PERFORM_DISC_UPDATE, IDM_LIST_INSTALL_WAD, IDM_LIST_UNINSTALL_WAD, IDM_IMPORT_NAND,