Merge pull request #5746 from leoetlino/disc-updates
Add support for installing disc updates from the game list
This commit is contained in:
commit
3748384008
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ set(SRCS
|
|||
NANDImporter.cpp
|
||||
TGCBlob.cpp
|
||||
Volume.cpp
|
||||
VolumeFileBlobReader.cpp
|
||||
VolumeGC.cpp
|
||||
VolumeWad.cpp
|
||||
VolumeWii.cpp
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue