Merge pull request from leoetlino/disc-updates

Add support for installing disc updates from the game list
This commit is contained in:
Leo Lam 2017-08-16 19:02:42 +08:00 committed by GitHub
commit 3748384008
17 changed files with 491 additions and 119 deletions

View File

@ -5,7 +5,10 @@
#include "Core/WiiUtils.h"
#include <algorithm>
#include <bitset>
#include <cinttypes>
#include <cstddef>
#include <cstring>
#include <map>
#include <memory>
#include <optional>
@ -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<u8>& response) const;
bool ShouldInstallTitle(const TitleInfo& title);
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
std::unordered_set<u64>* updated_titles);
@ -241,6 +248,14 @@ OnlineSystemUpdater::ParseTitlesResponse(const std::vector<u8>& 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"(<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@ -493,6 +508,163 @@ std::optional<std::vector<u8>> 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<DiscIO::Volume> m_volume;
std::unique_ptr<DiscIO::FileSystem> 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<DiscIO::FileInfo> 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<u8> 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<const char*>(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<DiscIO::FileInfo> 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;
}
}

View File

@ -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<bool(size_t processed, size_t total, u64 ti
// If no region is specified, the region of the installed System Menu will be used.
// If no region is specified and no system menu is installed, the update will fail.
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region);
// Perform a disc update with behaviour similar to the System Menu.
UpdateResult DoDiscUpdate(UpdateCallback update_callback, const std::string& image_path);
}

View File

@ -15,6 +15,7 @@ set(SRCS
NANDImporter.cpp
TGCBlob.cpp
Volume.cpp
VolumeFileBlobReader.cpp
VolumeGC.cpp
VolumeWad.cpp
VolumeWii.cpp

View File

@ -51,6 +51,7 @@
<ClCompile Include="NANDImporter.cpp" />
<ClCompile Include="TGCBlob.cpp" />
<ClCompile Include="Volume.cpp" />
<ClCompile Include="VolumeFileBlobReader.cpp" />
<ClCompile Include="VolumeGC.cpp" />
<ClCompile Include="VolumeWad.cpp" />
<ClCompile Include="VolumeWii.cpp" />
@ -73,6 +74,7 @@
<ClInclude Include="NANDImporter.h" />
<ClInclude Include="TGCBlob.h" />
<ClInclude Include="Volume.h" />
<ClInclude Include="VolumeFileBlobReader.h" />
<ClInclude Include="VolumeGC.h" />
<ClInclude Include="VolumeWad.h" />
<ClInclude Include="VolumeWii.h" />
@ -96,4 +98,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -60,6 +60,9 @@
<ClCompile Include="DirectoryBlob.cpp">
<Filter>Volume\Blob</Filter>
</ClCompile>
<ClCompile Include="VolumeFileBlobReader.cpp">
<Filter>Volume</Filter>
</ClCompile>
<ClCompile Include="VolumeGC.cpp">
<Filter>Volume</Filter>
</ClCompile>
@ -125,6 +128,9 @@
<ClInclude Include="DirectoryBlob.h">
<Filter>Volume\Blob</Filter>
</ClInclude>
<ClInclude Include="VolumeFileBlobReader.h">
<Filter>Volume</Filter>
</ClInclude>
<ClInclude Include="VolumeGC.h">
<Filter>Volume</Filter>
</ClInclude>
@ -147,4 +153,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -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> VolumeFileBlobReader::Create(const Volume& volume,
const FileSystem& file_system,
const std::string& file_path)
{
if (!file_system.IsValid())
return nullptr;
std::unique_ptr<FileInfo> file_info = file_system.FindFileInfo(file_path);
if (!file_info || file_info->IsDirectory())
return nullptr;
return std::unique_ptr<VolumeFileBlobReader>{
new VolumeFileBlobReader(volume, file_system, std::move(file_info))};
}
VolumeFileBlobReader::VolumeFileBlobReader(const Volume& volume, const FileSystem& file_system,
std::unique_ptr<FileInfo> 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

View File

@ -0,0 +1,38 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#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<VolumeFileBlobReader>
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<FileInfo> file_info);
const Volume& m_volume;
const FileSystem& m_file_system;
std::unique_ptr<FileInfo> m_file_info;
};
} // namespace

View File

@ -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<BlobReader> blob_reader) : m_reader(std::move(blob_reader))
{
if (m_reader)
m_valid = ParseWAD();
}
WiiWAD::~WiiWAD() = default;
bool WiiWAD::ParseWAD()
{
if (!IsWiiWAD(*m_reader))

View File

@ -19,6 +19,7 @@ class WiiWAD
{
public:
explicit WiiWAD(const std::string& name);
explicit WiiWAD(std::unique_ptr<BlobReader> 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<BlobReader> m_reader;

View File

@ -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);

View File

@ -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<WiiUtils::UpdateResult> 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<int>(total));
dialog.setValue(static_cast<int>(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 <typename Callable, typename... Args>
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<WiiUtils::UpdateResult> 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<int>(total));
dialog.setValue(static_cast<int>(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>(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

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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<WiiUtils::UpdateResult> 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 <typename Callable, typename... Args>
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<WiiUtils::UpdateResult> 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>(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();
}

View File

@ -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 =

View File

@ -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,