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 "Core/WiiUtils.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <bitset>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -32,15 +35,19 @@
|
||||||
#include "Core/IOS/ES/ES.h"
|
#include "Core/IOS/ES/ES.h"
|
||||||
#include "Core/IOS/ES/Formats.h"
|
#include "Core/IOS/ES/Formats.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
|
#include "DiscIO/DiscExtractor.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
#include "DiscIO/Filesystem.h"
|
||||||
#include "DiscIO/NANDContentLoader.h"
|
#include "DiscIO/NANDContentLoader.h"
|
||||||
|
#include "DiscIO/Volume.h"
|
||||||
|
#include "DiscIO/VolumeFileBlobReader.h"
|
||||||
|
#include "DiscIO/VolumeWii.h"
|
||||||
#include "DiscIO/WiiWad.h"
|
#include "DiscIO/WiiWad.h"
|
||||||
|
|
||||||
namespace WiiUtils
|
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())
|
if (!wad.IsValid())
|
||||||
{
|
{
|
||||||
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
|
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();
|
const auto tmd = wad.GetTMD();
|
||||||
IOS::HLE::Kernel ios;
|
|
||||||
const auto es = ios.GetES();
|
const auto es = ios.GetES();
|
||||||
|
|
||||||
IOS::HLE::Device::ES::Context context;
|
IOS::HLE::Device::ES::Context context;
|
||||||
|
@ -99,10 +105,19 @@ bool InstallWAD(const std::string& wad_path)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiscIO::NANDContentManager::Access().ClearCache();
|
|
||||||
return true;
|
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.
|
// Common functionality for system updaters.
|
||||||
class SystemUpdater
|
class SystemUpdater
|
||||||
{
|
{
|
||||||
|
@ -118,7 +133,6 @@ protected:
|
||||||
|
|
||||||
std::string GetDeviceRegion();
|
std::string GetDeviceRegion();
|
||||||
std::string GetDeviceId();
|
std::string GetDeviceId();
|
||||||
bool ShouldInstallTitle(const TitleInfo& title);
|
|
||||||
|
|
||||||
IOS::HLE::Kernel m_ios;
|
IOS::HLE::Kernel m_ios;
|
||||||
};
|
};
|
||||||
|
@ -149,14 +163,6 @@ std::string SystemUpdater::GetDeviceId()
|
||||||
return StringFromFormat("%" PRIu64, (u64(1) << 32) | ios_device_id);
|
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
|
class OnlineSystemUpdater final : public SystemUpdater
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -172,6 +178,7 @@ private:
|
||||||
|
|
||||||
Response GetSystemTitles();
|
Response GetSystemTitles();
|
||||||
Response ParseTitlesResponse(const std::vector<u8>& response) const;
|
Response ParseTitlesResponse(const std::vector<u8>& response) const;
|
||||||
|
bool ShouldInstallTitle(const TitleInfo& title);
|
||||||
|
|
||||||
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
|
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
|
||||||
std::unordered_set<u64>* updated_titles);
|
std::unordered_set<u64>* updated_titles);
|
||||||
|
@ -241,6 +248,14 @@ OnlineSystemUpdater::ParseTitlesResponse(const std::vector<u8>& response) const
|
||||||
return info;
|
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"?>
|
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/"
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
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);
|
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)
|
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region)
|
||||||
{
|
{
|
||||||
OnlineSystemUpdater updater{std::move(update_callback), 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();
|
DiscIO::NANDContentManager::Access().ClearCache();
|
||||||
return result;
|
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,
|
Succeeded,
|
||||||
AlreadyUpToDate,
|
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.
|
// NUS errors and failures.
|
||||||
ServerFailed,
|
ServerFailed,
|
||||||
// General download failures.
|
// General download failures.
|
||||||
DownloadFailed,
|
DownloadFailed,
|
||||||
|
|
||||||
// Import failures.
|
// Import failures.
|
||||||
ImportFailed,
|
ImportFailed,
|
||||||
// Update was cancelled.
|
// 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, 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.
|
// If no region is specified and no system menu is installed, the update will fail.
|
||||||
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region);
|
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
|
NANDImporter.cpp
|
||||||
TGCBlob.cpp
|
TGCBlob.cpp
|
||||||
Volume.cpp
|
Volume.cpp
|
||||||
|
VolumeFileBlobReader.cpp
|
||||||
VolumeGC.cpp
|
VolumeGC.cpp
|
||||||
VolumeWad.cpp
|
VolumeWad.cpp
|
||||||
VolumeWii.cpp
|
VolumeWii.cpp
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
<ClCompile Include="NANDImporter.cpp" />
|
<ClCompile Include="NANDImporter.cpp" />
|
||||||
<ClCompile Include="TGCBlob.cpp" />
|
<ClCompile Include="TGCBlob.cpp" />
|
||||||
<ClCompile Include="Volume.cpp" />
|
<ClCompile Include="Volume.cpp" />
|
||||||
|
<ClCompile Include="VolumeFileBlobReader.cpp" />
|
||||||
<ClCompile Include="VolumeGC.cpp" />
|
<ClCompile Include="VolumeGC.cpp" />
|
||||||
<ClCompile Include="VolumeWad.cpp" />
|
<ClCompile Include="VolumeWad.cpp" />
|
||||||
<ClCompile Include="VolumeWii.cpp" />
|
<ClCompile Include="VolumeWii.cpp" />
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
<ClInclude Include="NANDImporter.h" />
|
<ClInclude Include="NANDImporter.h" />
|
||||||
<ClInclude Include="TGCBlob.h" />
|
<ClInclude Include="TGCBlob.h" />
|
||||||
<ClInclude Include="Volume.h" />
|
<ClInclude Include="Volume.h" />
|
||||||
|
<ClInclude Include="VolumeFileBlobReader.h" />
|
||||||
<ClInclude Include="VolumeGC.h" />
|
<ClInclude Include="VolumeGC.h" />
|
||||||
<ClInclude Include="VolumeWad.h" />
|
<ClInclude Include="VolumeWad.h" />
|
||||||
<ClInclude Include="VolumeWii.h" />
|
<ClInclude Include="VolumeWii.h" />
|
||||||
|
|
|
@ -60,6 +60,9 @@
|
||||||
<ClCompile Include="DirectoryBlob.cpp">
|
<ClCompile Include="DirectoryBlob.cpp">
|
||||||
<Filter>Volume\Blob</Filter>
|
<Filter>Volume\Blob</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="VolumeFileBlobReader.cpp">
|
||||||
|
<Filter>Volume</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="VolumeGC.cpp">
|
<ClCompile Include="VolumeGC.cpp">
|
||||||
<Filter>Volume</Filter>
|
<Filter>Volume</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -125,6 +128,9 @@
|
||||||
<ClInclude Include="DirectoryBlob.h">
|
<ClInclude Include="DirectoryBlob.h">
|
||||||
<Filter>Volume\Blob</Filter>
|
<Filter>Volume\Blob</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="VolumeFileBlobReader.h">
|
||||||
|
<Filter>Volume</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="VolumeGC.h">
|
<ClInclude Include="VolumeGC.h">
|
||||||
<Filter>Volume</Filter>
|
<Filter>Volume</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
|
@ -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,20 +44,17 @@ bool IsWiiWAD(BlobReader& reader)
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
WiiWAD::WiiWAD(const std::string& name) : m_reader(CreateBlobReader(name))
|
WiiWAD::WiiWAD(const std::string& name) : WiiWAD(CreateBlobReader(name))
|
||||||
{
|
{
|
||||||
if (m_reader == nullptr)
|
}
|
||||||
{
|
|
||||||
m_valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
WiiWAD::WiiWAD(std::unique_ptr<BlobReader> blob_reader) : m_reader(std::move(blob_reader))
|
||||||
|
{
|
||||||
|
if (m_reader)
|
||||||
m_valid = ParseWAD();
|
m_valid = ParseWAD();
|
||||||
}
|
}
|
||||||
|
|
||||||
WiiWAD::~WiiWAD()
|
WiiWAD::~WiiWAD() = default;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WiiWAD::ParseWAD()
|
bool WiiWAD::ParseWAD()
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@ class WiiWAD
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit WiiWAD(const std::string& name);
|
explicit WiiWAD(const std::string& name);
|
||||||
|
explicit WiiWAD(std::unique_ptr<BlobReader> blob_reader);
|
||||||
~WiiWAD();
|
~WiiWAD();
|
||||||
|
|
||||||
bool IsValid() const { return m_valid; }
|
bool IsValid() const { return m_valid; }
|
||||||
|
@ -32,7 +33,7 @@ public:
|
||||||
private:
|
private:
|
||||||
bool ParseWAD();
|
bool ParseWAD();
|
||||||
|
|
||||||
bool m_valid;
|
bool m_valid = false;
|
||||||
|
|
||||||
std::unique_ptr<BlobReader> m_reader;
|
std::unique_ptr<BlobReader> m_reader;
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include "DolphinQt2/GameList/ListProxyModel.h"
|
#include "DolphinQt2/GameList/ListProxyModel.h"
|
||||||
#include "DolphinQt2/QtUtils/DoubleClickEventFilter.h"
|
#include "DolphinQt2/QtUtils/DoubleClickEventFilter.h"
|
||||||
#include "DolphinQt2/Settings.h"
|
#include "DolphinQt2/Settings.h"
|
||||||
|
#include "DolphinQt2/WiiUpdate.h"
|
||||||
|
|
||||||
static bool CompressCB(const std::string&, float, void*);
|
static bool CompressCB(const std::string&, float, void*);
|
||||||
|
|
||||||
|
@ -164,6 +165,15 @@ void GameList::ShowContextMenu(const QPoint&)
|
||||||
|
|
||||||
menu->addSeparator();
|
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)
|
if (platform == DiscIO::Platform::WII_WAD)
|
||||||
{
|
{
|
||||||
QAction* wad_install_action = new QAction(tr("Install to the NAND"), menu);
|
QAction* wad_install_action = new QAction(tr("Install to the NAND"), menu);
|
||||||
|
|
|
@ -22,66 +22,9 @@
|
||||||
|
|
||||||
namespace WiiUpdate
|
namespace WiiUpdate
|
||||||
{
|
{
|
||||||
void PerformOnlineUpdate(const std::string& region, QWidget* parent)
|
static void ShowResult(QWidget* parent, WiiUtils::UpdateResult result)
|
||||||
{
|
{
|
||||||
const int confirm = QMessageBox::question(
|
switch (result)
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
case WiiUtils::UpdateResult::Succeeded:
|
case WiiUtils::UpdateResult::Succeeded:
|
||||||
QMessageBox::information(parent, QObject::tr("Update completed"),
|
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 "
|
QObject::tr("The update has been cancelled. It is strongly recommended to "
|
||||||
"finish it in order to avoid inconsistent system software versions."));
|
"finish it in order to avoid inconsistent system software versions."));
|
||||||
break;
|
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
|
} // namespace WiiUpdate
|
||||||
|
|
|
@ -11,4 +11,5 @@ class QWidget;
|
||||||
namespace WiiUpdate
|
namespace WiiUpdate
|
||||||
{
|
{
|
||||||
void PerformOnlineUpdate(const std::string& region, QWidget* parent = nullptr);
|
void PerformOnlineUpdate(const std::string& region, QWidget* parent = nullptr);
|
||||||
|
void PerformDiscUpdate(const std::string& file_path, QWidget* parent = nullptr);
|
||||||
} // namespace WiiUpdate
|
} // namespace WiiUpdate
|
||||||
|
|
|
@ -245,6 +245,7 @@ BEGIN_EVENT_TABLE(CFrame, CRenderFrame)
|
||||||
EVT_MENU_RANGE(IDM_FLOAT_LOG_WINDOW, IDM_FLOAT_CODE_WINDOW, CFrame::OnFloatWindow)
|
EVT_MENU_RANGE(IDM_FLOAT_LOG_WINDOW, IDM_FLOAT_CODE_WINDOW, CFrame::OnFloatWindow)
|
||||||
|
|
||||||
// Game list context menu
|
// 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_INSTALL_WAD, CFrame::OnInstallWAD)
|
||||||
EVT_MENU(IDM_LIST_UNINSTALL_WAD, CFrame::OnUninstallWAD)
|
EVT_MENU(IDM_LIST_UNINSTALL_WAD, CFrame::OnUninstallWAD)
|
||||||
|
|
||||||
|
|
|
@ -349,6 +349,7 @@ private:
|
||||||
void OnImportBootMiiBackup(wxCommandEvent& event);
|
void OnImportBootMiiBackup(wxCommandEvent& event);
|
||||||
void OnExtractCertificates(wxCommandEvent& event);
|
void OnExtractCertificates(wxCommandEvent& event);
|
||||||
void OnPerformOnlineWiiUpdate(wxCommandEvent& event);
|
void OnPerformOnlineWiiUpdate(wxCommandEvent& event);
|
||||||
|
void OnPerformDiscWiiUpdate(wxCommandEvent& event);
|
||||||
void OnFifoPlayer(wxCommandEvent& event);
|
void OnFifoPlayer(wxCommandEvent& event);
|
||||||
void OnConnectWiimote(wxCommandEvent& event);
|
void OnConnectWiimote(wxCommandEvent& event);
|
||||||
void GameListChanged(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?"),
|
switch (result)
|
||||||
_("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())
|
|
||||||
{
|
{
|
||||||
case WiiUtils::UpdateResult::Succeeded:
|
case WiiUtils::UpdateResult::Succeeded:
|
||||||
wxMessageBox(_("The emulated Wii console has been updated."), _("Update completed"),
|
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."),
|
"finish it in order to avoid inconsistent system software versions."),
|
||||||
_("Update cancelled"), wxOK | wxICON_WARNING);
|
_("Update cancelled"), wxOK | wxICON_WARNING);
|
||||||
break;
|
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();
|
UpdateLoadWiiMenuItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1173,6 +1173,13 @@ void GameListCtrl::OnRightClick(wxMouseEvent& event)
|
||||||
changeDiscItem->Enable(Core::IsRunning());
|
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)
|
if (platform == DiscIO::Platform::WII_WAD)
|
||||||
{
|
{
|
||||||
auto* const install_wad_item =
|
auto* const install_wad_item =
|
||||||
|
|
|
@ -100,6 +100,7 @@ enum
|
||||||
IDM_GAME_WIKI,
|
IDM_GAME_WIKI,
|
||||||
IDM_LOAD_WII_MENU,
|
IDM_LOAD_WII_MENU,
|
||||||
IDM_MENU_INSTALL_WAD,
|
IDM_MENU_INSTALL_WAD,
|
||||||
|
IDM_LIST_PERFORM_DISC_UPDATE,
|
||||||
IDM_LIST_INSTALL_WAD,
|
IDM_LIST_INSTALL_WAD,
|
||||||
IDM_LIST_UNINSTALL_WAD,
|
IDM_LIST_UNINSTALL_WAD,
|
||||||
IDM_IMPORT_NAND,
|
IDM_IMPORT_NAND,
|
||||||
|
|
Loading…
Reference in New Issue