Merge pull request #5610 from leoetlino/online-wii-updates

Add ability to perform Wii online updates (without the system menu)
This commit is contained in:
Leo Lam 2017-06-28 15:35:05 +02:00 committed by GitHub
commit e14a82a87e
23 changed files with 870 additions and 114 deletions

View File

@ -19,6 +19,7 @@ set(SRCS
State.cpp
TitleDatabase.cpp
WiiRoot.cpp
WiiUtils.cpp
Boot/Boot_BS2Emu.cpp
Boot/Boot.cpp
Boot/Boot_WiiWAD.cpp

View File

@ -290,6 +290,7 @@
<ClCompile Include="State.cpp" />
<ClCompile Include="TitleDatabase.cpp" />
<ClCompile Include="WiiRoot.cpp" />
<ClCompile Include="WiiUtils.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ActionReplay.h" />
@ -524,6 +525,7 @@
<ClInclude Include="Titles.h" />
<ClInclude Include="TitleDatabase.h" />
<ClInclude Include="WiiRoot.h" />
<ClInclude Include="WiiUtils.h" />
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />

View File

@ -179,6 +179,7 @@
<ClCompile Include="State.cpp" />
<ClCompile Include="TitleDatabase.cpp" />
<ClCompile Include="WiiRoot.cpp" />
<ClCompile Include="WiiUtils.cpp" />
<ClCompile Include="ActionReplay.cpp">
<Filter>ActionReplay</Filter>
</ClCompile>
@ -898,6 +899,7 @@
<ClInclude Include="Titles.h" />
<ClInclude Include="TitleDatabase.h" />
<ClInclude Include="WiiRoot.h" />
<ClInclude Include="WiiUtils.h" />
<ClInclude Include="ActionReplay.h">
<Filter>ActionReplay</Filter>
</ClInclude>
@ -1543,4 +1545,4 @@
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
</Project>
</Project>

View File

@ -843,30 +843,21 @@ ReturnCode ES::ReadCertStore(std::vector<u8>* buffer) const
ReturnCode ES::WriteNewCertToStore(const IOS::ES::CertReader& cert)
{
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
// The certificate store file may not exist, so we use a+b and not r+b here.
File::IOFile store_file{store_path, "a+b"};
if (!store_file)
return ES_EIO;
// Read the current store to determine if the new cert needs to be written.
const u64 file_size = store_file.GetSize();
if (file_size != 0)
std::vector<u8> current_store;
const ReturnCode ret = ReadCertStore(&current_store);
if (ret == IPC_SUCCESS)
{
std::vector<u8> certs_bytes(file_size);
if (!store_file.ReadBytes(certs_bytes.data(), certs_bytes.size()))
return ES_SHORT_READ;
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(certs_bytes);
const std::map<std::string, IOS::ES::CertReader> certs = IOS::ES::ParseCertChain(current_store);
// The cert is already present in the store. Nothing to do.
if (certs.find(cert.GetName()) != certs.end())
return IPC_SUCCESS;
}
// Otherwise, write the new cert at the end of the store.
// When opening a file in read-write mode, a seek is required before a write.
store_file.Seek(0, SEEK_END);
if (!store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
const std::string store_path = Common::RootUserPath(Common::FROM_SESSION_ROOT) + "/sys/cert.sys";
File::IOFile store_file{store_path, "ab"};
if (!store_file || !store_file.WriteBytes(cert.GetBytes().data(), cert.GetBytes().size()))
return ES_EIO;
return IPC_SUCCESS;
}

View File

@ -0,0 +1,498 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/WiiUtils.h"
#include <algorithm>
#include <cinttypes>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <unordered_set>
#include <utility>
#include <vector>
#include <pugixml.hpp>
#include "Common/Assert.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileUtil.h"
#include "Common/HttpRequest.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
#include "Core/CommonTitles.h"
#include "Core/ConfigManager.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IOS.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h"
#include "DiscIO/WiiWad.h"
namespace WiiUtils
{
bool InstallWAD(const std::string& wad_path)
{
const DiscIO::WiiWAD wad{wad_path};
if (!wad.IsValid())
{
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
return false;
}
const auto tmd = wad.GetTMD();
IOS::HLE::Kernel ios;
const auto es = ios.GetES();
IOS::HLE::Device::ES::Context context;
IOS::HLE::ReturnCode ret;
const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks;
while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 ||
(ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0)
{
if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE &&
AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?"))
{
SConfig::GetInstance().m_enable_signature_checks = false;
continue;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
PanicAlertT("WAD installation failed: Could not initialise title import.");
return false;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
const bool contents_imported = [&]() {
const u64 title_id = tmd.GetTitleId();
for (const IOS::ES::Content& content : tmd.GetContents())
{
const std::vector<u8> data = wad.GetContent(content.index);
if (es->ImportContentBegin(context, title_id, content.id) < 0 ||
es->ImportContentData(context, 0, data.data(), static_cast<u32>(data.size())) < 0 ||
es->ImportContentEnd(context, 0) < 0)
{
PanicAlertT("WAD installation failed: Could not import content %08x.", content.id);
return false;
}
}
return true;
}();
if ((contents_imported && es->ImportTitleDone(context) < 0) ||
(!contents_imported && es->ImportTitleCancel(context) < 0))
{
PanicAlertT("WAD installation failed: Could not finalise title import.");
return false;
}
DiscIO::NANDContentManager::Access().ClearCache();
return true;
}
// Common functionality for system updaters.
class SystemUpdater
{
public:
virtual ~SystemUpdater() = default;
protected:
struct TitleInfo
{
u64 id;
u16 version;
};
std::string GetDeviceRegion();
std::string GetDeviceId();
bool ShouldInstallTitle(const TitleInfo& title);
IOS::HLE::Kernel m_ios;
};
std::string SystemUpdater::GetDeviceRegion()
{
// Try to determine the region from an installed system menu.
const auto tmd = m_ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
if (tmd.IsValid())
{
const DiscIO::Region region = tmd.GetRegion();
static const std::map<DiscIO::Region, std::string> regions = {
{DiscIO::Region::NTSC_J, "JPN"},
{DiscIO::Region::NTSC_U, "USA"},
{DiscIO::Region::PAL, "EUR"},
{DiscIO::Region::NTSC_K, "KOR"},
{DiscIO::Region::UNKNOWN_REGION, "EUR"}};
return regions.at(region);
}
return "";
}
std::string SystemUpdater::GetDeviceId()
{
u32 ios_device_id;
if (m_ios.GetES()->GetDeviceId(&ios_device_id) < 0)
return "";
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:
OnlineSystemUpdater(UpdateCallback update_callback, const std::string& region);
UpdateResult DoOnlineUpdate();
private:
struct Response
{
std::string content_prefix_url;
std::vector<TitleInfo> titles;
};
Response GetSystemTitles();
Response ParseTitlesResponse(const std::vector<u8>& response) const;
UpdateResult InstallTitleFromNUS(const std::string& prefix_url, const TitleInfo& title,
std::unordered_set<u64>* updated_titles);
// Helper functions to download contents from NUS.
std::pair<IOS::ES::TMDReader, std::vector<u8>> DownloadTMD(const std::string& prefix_url,
const TitleInfo& title);
std::pair<std::vector<u8>, std::vector<u8>> DownloadTicket(const std::string& prefix_url,
const TitleInfo& title);
std::optional<std::vector<u8>> DownloadContent(const std::string& prefix_url,
const TitleInfo& title, u32 cid);
UpdateCallback m_update_callback;
std::string m_requested_region;
Common::HttpRequest m_http{std::chrono::minutes{3}};
};
OnlineSystemUpdater::OnlineSystemUpdater(UpdateCallback update_callback, const std::string& region)
: m_update_callback(std::move(update_callback)), m_requested_region(region)
{
}
OnlineSystemUpdater::Response
OnlineSystemUpdater::ParseTitlesResponse(const std::vector<u8>& response) const
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(response.data(), response.size());
if (!result)
{
ERROR_LOG(CORE, "ParseTitlesResponse: Could not parse response");
return {};
}
// pugixml doesn't fully support namespaces and ignores them.
const pugi::xml_node node = doc.select_node("//GetSystemUpdateResponse").node();
if (!node)
{
ERROR_LOG(CORE, "ParseTitlesResponse: Could not find response node");
return {};
}
const int code = node.child("ErrorCode").text().as_int();
if (code != 0)
{
ERROR_LOG(CORE, "ParseTitlesResponse: Non-zero error code (%d)", code);
return {};
}
// libnup uses the uncached URL, not the cached one. However, that one is way, way too slow,
// so let's use the cached endpoint.
Response info;
info.content_prefix_url = node.child("ContentPrefixURL").text().as_string();
// Disable HTTPS because we can't use it without a device certificate.
info.content_prefix_url = ReplaceAll(info.content_prefix_url, "https://", "http://");
if (info.content_prefix_url.empty())
{
ERROR_LOG(CORE, "ParseTitlesResponse: Empty content prefix URL");
return {};
}
for (const pugi::xml_node& title_node : node.children("TitleVersion"))
{
const u64 title_id = std::stoull(title_node.child("TitleId").text().as_string(), nullptr, 16);
const u16 title_version = static_cast<u16>(title_node.child("Version").text().as_uint());
info.titles.push_back({title_id, title_version});
}
return info;
}
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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<GetSystemUpdateRequest xmlns="urn:nus.wsapi.broadon.com">
<Version>1.0</Version>
<MessageId>0</MessageId>
<DeviceId></DeviceId>
<RegionId></RegionId>
</GetSystemUpdateRequest>
</soapenv:Body>
</soapenv:Envelope>
)";
OnlineSystemUpdater::Response OnlineSystemUpdater::GetSystemTitles()
{
// Construct the request by loading the template first, then updating some fields.
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(GET_SYSTEM_TITLES_REQUEST_PAYLOAD);
_assert_(result);
// Nintendo does not really care about the device ID or verify that we *are* that device,
// as long as it is a valid Wii device ID.
const std::string device_id = GetDeviceId();
_assert_(doc.select_node("//DeviceId").node().text().set(device_id.c_str()));
// Write the correct device region.
const std::string region = m_requested_region.empty() ? GetDeviceRegion() : m_requested_region;
_assert_(doc.select_node("//RegionId").node().text().set(region.c_str()));
std::ostringstream stream;
doc.save(stream);
const std::string request = stream.str();
// Note: We don't use HTTPS because that would require the user to have
// a device certificate which cannot be redistributed with Dolphin.
// This is fine, because IOS has signature checks.
const Common::HttpRequest::Response response =
m_http.Post("http://nus.shop.wii.com/nus/services/NetUpdateSOAP", request,
{
{"SOAPAction", "urn:nus.wsapi.broadon.com/GetSystemUpdate"},
{"User-Agent", "wii libnup/1.0"},
{"Content-Type", "text/xml; charset=utf-8"},
});
if (!response)
return {};
return ParseTitlesResponse(*response);
}
UpdateResult OnlineSystemUpdater::DoOnlineUpdate()
{
const Response info = GetSystemTitles();
if (info.titles.empty())
return UpdateResult::ServerFailed;
// Download and install any title that is older than the NUS version.
// The order is determined by the server response, which is: boot2, System Menu, IOSes, channels.
// As we install any IOS required by titles, the real order is boot2, SM IOS, SM, IOSes, channels.
std::unordered_set<u64> updated_titles;
size_t processed = 0;
for (const TitleInfo& title : info.titles)
{
if (!m_update_callback(processed++, info.titles.size(), title.id))
return UpdateResult::Cancelled;
const UpdateResult res = InstallTitleFromNUS(info.content_prefix_url, title, &updated_titles);
if (res != UpdateResult::Succeeded)
{
ERROR_LOG(CORE, "Failed to update %016" PRIx64 " -- aborting update", title.id);
return res;
}
m_update_callback(processed, info.titles.size(), title.id);
}
if (updated_titles.empty())
{
NOTICE_LOG(CORE, "Update finished - Already up-to-date");
return UpdateResult::AlreadyUpToDate;
}
NOTICE_LOG(CORE, "Update finished - %zu updates installed", updated_titles.size());
return UpdateResult::Succeeded;
}
UpdateResult OnlineSystemUpdater::InstallTitleFromNUS(const std::string& prefix_url,
const TitleInfo& title,
std::unordered_set<u64>* updated_titles)
{
// We currently don't support boot2 updates at all, so ignore any attempt to install it.
if (title.id == Titles::BOOT2)
return UpdateResult::Succeeded;
if (!ShouldInstallTitle(title) || updated_titles->find(title.id) != updated_titles->end())
return UpdateResult::Succeeded;
NOTICE_LOG(CORE, "Updating title %016" PRIx64, title.id);
// Download the ticket and certificates.
const auto ticket = DownloadTicket(prefix_url, title);
if (ticket.first.empty() || ticket.second.empty())
{
ERROR_LOG(CORE, "Failed to download ticket and certs");
return UpdateResult::DownloadFailed;
}
// Import the ticket.
IOS::HLE::ReturnCode ret = IOS::HLE::IPC_SUCCESS;
const auto es = m_ios.GetES();
if ((ret = es->ImportTicket(ticket.first, ticket.second)) < 0)
{
ERROR_LOG(CORE, "Failed to import ticket: error %d", ret);
return UpdateResult::ImportFailed;
}
// Download the TMD.
const auto tmd = DownloadTMD(prefix_url, title);
if (!tmd.first.IsValid())
{
ERROR_LOG(CORE, "Failed to download TMD");
return UpdateResult::DownloadFailed;
}
// Download and import any required system title first.
const u64 ios_id = tmd.first.GetIOSId();
if (ios_id != 0 && IOS::ES::IsTitleType(ios_id, IOS::ES::TitleType::System))
{
if (!es->FindInstalledTMD(ios_id).IsValid())
{
WARN_LOG(CORE, "Importing required system title %016" PRIx64 " first", ios_id);
const UpdateResult res = InstallTitleFromNUS(prefix_url, {ios_id, 0}, updated_titles);
if (res != UpdateResult::Succeeded)
{
ERROR_LOG(CORE, "Failed to import required system title %016" PRIx64, ios_id);
return res;
}
}
}
// Initialise the title import.
IOS::HLE::Device::ES::Context context;
if ((ret = es->ImportTitleInit(context, tmd.first.GetBytes(), tmd.second)) < 0)
{
ERROR_LOG(CORE, "Failed to initialise title import: error %d", ret);
return UpdateResult::ImportFailed;
}
// Now download and install contents listed in the TMD.
const std::vector<IOS::ES::Content> stored_contents = es->GetStoredContentsFromTMD(tmd.first);
const UpdateResult import_result = [&]() {
for (const IOS::ES::Content& content : tmd.first.GetContents())
{
const bool is_already_installed = std::find_if(stored_contents.begin(), stored_contents.end(),
[&content](const auto& stored_content) {
return stored_content.id == content.id;
}) != stored_contents.end();
// Do skip what is already installed on the NAND.
if (is_already_installed)
continue;
if ((ret = es->ImportContentBegin(context, title.id, content.id)) < 0)
{
ERROR_LOG(CORE, "Failed to initialise import for content %08x: error %d", content.id, ret);
return UpdateResult::ImportFailed;
}
const std::optional<std::vector<u8>> data = DownloadContent(prefix_url, title, content.id);
if (!data)
{
ERROR_LOG(CORE, "Failed to download content %08x", content.id);
return UpdateResult::DownloadFailed;
}
if (es->ImportContentData(context, 0, data->data(), static_cast<u32>(data->size())) < 0 ||
es->ImportContentEnd(context, 0) < 0)
{
ERROR_LOG(CORE, "Failed to import content %08x", content.id);
return UpdateResult::ImportFailed;
}
}
return UpdateResult::Succeeded;
}();
const bool all_contents_imported = import_result == UpdateResult::Succeeded;
if ((all_contents_imported && (ret = es->ImportTitleDone(context)) < 0) ||
(!all_contents_imported && (ret = es->ImportTitleCancel(context)) < 0))
{
ERROR_LOG(CORE, "Failed to finalise title import: error %d", ret);
return UpdateResult::ImportFailed;
}
if (!all_contents_imported)
return import_result;
updated_titles->emplace(title.id);
return UpdateResult::Succeeded;
}
std::pair<IOS::ES::TMDReader, std::vector<u8>>
OnlineSystemUpdater::DownloadTMD(const std::string& prefix_url, const TitleInfo& title)
{
const std::string url =
(title.version == 0) ?
prefix_url + StringFromFormat("/%016" PRIx64 "/tmd", title.id) :
prefix_url + StringFromFormat("/%016" PRIx64 "/tmd.%u", title.id, title.version);
const Common::HttpRequest::Response response = m_http.Get(url);
if (!response)
return {};
// Too small to contain both the TMD and a cert chain.
if (response->size() <= sizeof(IOS::ES::TMDHeader))
return {};
const size_t tmd_size =
sizeof(IOS::ES::TMDHeader) +
sizeof(IOS::ES::Content) *
Common::swap16(response->data() + offsetof(IOS::ES::TMDHeader, num_contents));
if (response->size() <= tmd_size)
return {};
const auto tmd_begin = response->begin();
const auto tmd_end = tmd_begin + tmd_size;
return {IOS::ES::TMDReader(std::vector<u8>(tmd_begin, tmd_end)),
std::vector<u8>(tmd_end, response->end())};
}
std::pair<std::vector<u8>, std::vector<u8>>
OnlineSystemUpdater::DownloadTicket(const std::string& prefix_url, const TitleInfo& title)
{
const std::string url = prefix_url + StringFromFormat("/%016" PRIx64 "/cetk", title.id);
const Common::HttpRequest::Response response = m_http.Get(url);
if (!response)
return {};
// Too small to contain both the ticket and a cert chain.
if (response->size() <= sizeof(IOS::ES::Ticket))
return {};
const auto ticket_begin = response->begin();
const auto ticket_end = ticket_begin + sizeof(IOS::ES::Ticket);
return {std::vector<u8>(ticket_begin, ticket_end), std::vector<u8>(ticket_end, response->end())};
}
std::optional<std::vector<u8>> OnlineSystemUpdater::DownloadContent(const std::string& prefix_url,
const TitleInfo& title, u32 cid)
{
const std::string url = prefix_url + StringFromFormat("/%016" PRIx64 "/%08x", title.id, cid);
return m_http.Get(url);
}
UpdateResult DoOnlineUpdate(UpdateCallback update_callback, const std::string& region)
{
OnlineSystemUpdater updater{std::move(update_callback), region};
const UpdateResult result = updater.DoOnlineUpdate();
DiscIO::NANDContentManager::Access().ClearCache();
return result;
}
}

View File

@ -0,0 +1,40 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <functional>
#include <string>
#include "Common/CommonTypes.h"
// Small utility functions for common Wii related tasks.
namespace WiiUtils
{
bool InstallWAD(const std::string& wad_path);
enum class UpdateResult
{
Succeeded,
AlreadyUpToDate,
// NUS errors and failures.
ServerFailed,
// General download failures.
DownloadFailed,
// Import failures.
ImportFailed,
// Update was cancelled.
Cancelled,
};
// Return false to cancel the update as soon as the current title has finished updating.
using UpdateCallback = std::function<bool(size_t processed, size_t total, u64 title_id)>;
// Download and install the latest version of all titles (if missing) from NUS.
// 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);
}

View File

@ -19,6 +19,8 @@ set(SRCS
Resources.cpp
Settings.cpp
ToolBar.cpp
WiiUpdate.cpp
WiiUpdate.h
Config/ControllersWindow.cpp
Config/FilesystemWidget.cpp
Config/InfoWidget.cpp

View File

@ -164,6 +164,7 @@
<ClCompile Include="Settings\InterfacePane.cpp" />
<ClCompile Include="Settings\PathPane.cpp" />
<ClCompile Include="ToolBar.cpp" />
<ClCompile Include="WiiUpdate.cpp" />
</ItemGroup>
<!--Put standard C/C++ headers here-->
<ItemGroup>
@ -185,6 +186,7 @@
<ClInclude Include="QtUtils\ElidedButton.h" />
<ClInclude Include="Resources.h" />
<ClInclude Include="Settings\PathPane.h" />
<ClInclude Include="WiiUpdate.h" />
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />

View File

@ -15,6 +15,7 @@
#include "Core/HW/WiiSaveCrypted.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/IOS.h"
#include "Core/WiiUtils.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h"
@ -22,7 +23,6 @@
#include "DolphinQt2/GameList/GameFile.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
#include "UICommon/WiiUtils.h"
static const int CACHE_VERSION = 13; // Last changed in PR #3261
static const int DATASTREAM_VERSION = QDataStream::Qt_5_5;

View File

@ -30,7 +30,6 @@
#include "DolphinQt2/AboutDialog.h"
#include "DolphinQt2/Config/ControllersWindow.h"
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
#include "DolphinQt2/Config/SettingsWindow.h"
#include "DolphinQt2/Host.h"
@ -39,6 +38,7 @@
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
#include "DolphinQt2/WiiUpdate.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
@ -165,6 +165,9 @@ void MainWindow::ConnectMenuBar()
// Options
connect(m_menu_bar, &MenuBar::ConfigureHotkeys, this, &MainWindow::ShowHotkeyDialog);
// Tools
connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
// View
connect(m_menu_bar, &MenuBar::ShowTable, m_game_list, &GameList::SetTableView);
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
@ -530,6 +533,13 @@ void MainWindow::SetStateSlot(int slot)
m_state_slot = slot;
}
void MainWindow::PerformOnlineUpdate(const std::string& region)
{
WiiUpdate::PerformOnlineUpdate(region, this);
// Since the update may have installed a newer system menu, refresh the tools menu.
m_menu_bar->UpdateToolsMenu(false);
}
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
if (event->type() == QEvent::Close && !Stop())

View File

@ -56,6 +56,8 @@ private slots:
void StateSaveOldest();
void SetStateSlot(int slot);
void PerformOnlineUpdate(const std::string& region);
void FullScreen();
void ScreenShot();

View File

@ -9,6 +9,9 @@
#include <QMessageBox>
#include <QUrl>
#include "Core/CommonTitles.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/IOS.h"
#include "Core/State.h"
#include "DolphinQt2/AboutDialog.h"
#include "DolphinQt2/GameList/GameFile.h"
@ -43,6 +46,7 @@ void MenuBar::EmulationStarted()
m_state_load_menu->setEnabled(true);
m_state_save_menu->setEnabled(true);
UpdateStateSlotMenu();
UpdateToolsMenu(true);
}
void MenuBar::EmulationPaused()
{
@ -66,6 +70,7 @@ void MenuBar::EmulationStopped()
m_state_load_menu->setEnabled(false);
m_state_save_menu->setEnabled(false);
UpdateStateSlotMenu();
UpdateToolsMenu(false);
}
void MenuBar::AddFileMenu()
@ -79,6 +84,17 @@ void MenuBar::AddToolsMenu()
{
QMenu* tools_menu = addMenu(tr("Tools"));
m_wad_install_action = tools_menu->addAction(tr("Install WAD..."), this, SLOT(InstallWAD()));
m_perform_online_update_menu = tools_menu->addMenu(tr("Perform Online System Update"));
m_perform_online_update_for_current_region = m_perform_online_update_menu->addAction(
tr("Current Region"), [this] { emit PerformOnlineUpdate(""); });
m_perform_online_update_menu->addSeparator();
m_perform_online_update_menu->addAction(tr("Europe"),
[this] { emit PerformOnlineUpdate("EUR"); });
m_perform_online_update_menu->addAction(tr("Japan"), [this] { emit PerformOnlineUpdate("JPN"); });
m_perform_online_update_menu->addAction(tr("Korea"), [this] { emit PerformOnlineUpdate("KOR"); });
m_perform_online_update_menu->addAction(tr("United States"),
[this] { emit PerformOnlineUpdate("USA"); });
}
void MenuBar::AddEmulationMenu()
@ -248,6 +264,20 @@ void MenuBar::AddTableColumnsMenu(QMenu* view_menu)
}
}
void MenuBar::UpdateToolsMenu(bool emulation_started)
{
const bool enable_wii_tools = !emulation_started || !Settings::Instance().IsWiiGameRunning();
m_perform_online_update_menu->setEnabled(enable_wii_tools);
if (enable_wii_tools)
{
IOS::HLE::Kernel ios;
const auto tmd = ios.GetES()->FindInstalledTMD(Titles::SYSTEM_MENU);
for (QAction* action : m_perform_online_update_menu->actions())
action->setEnabled(!tmd.IsValid());
m_perform_online_update_for_current_region->setEnabled(tmd.IsValid());
}
}
void MenuBar::InstallWAD()
{
QString wad_file = QFileDialog::getOpenFileName(this, tr("Select a title to install to NAND"),

View File

@ -4,6 +4,8 @@
#pragma once
#include <string>
#include <QMenu>
#include <QMenuBar>
@ -38,6 +40,8 @@ signals:
void StateSaveOldest();
void SetStateSlot(int slot);
void PerformOnlineUpdate(const std::string& region);
// Options
void ConfigureHotkeys();
@ -53,6 +57,7 @@ public slots:
void EmulationPaused();
void EmulationStopped();
void UpdateStateSlotMenu();
void UpdateToolsMenu(bool emulation_started);
// Tools
void InstallWAD();
@ -79,6 +84,8 @@ private:
// Tools
QAction* m_wad_install_action;
QMenu* m_perform_online_update_menu;
QAction* m_perform_online_update_for_current_region;
// Emulation
QAction* m_play_action;

View File

@ -0,0 +1,119 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "DolphinQt2/WiiUpdate.h"
#include <cinttypes>
#include <future>
#include <QCloseEvent>
#include <QMessageBox>
#include <QObject>
#include <QProgressDialog>
#include <QPushButton>
#include "Common/FileUtil.h"
#include "Common/Flag.h"
#include "Core/Core.h"
#include "Core/WiiUtils.h"
#include "DiscIO/NANDImporter.h"
namespace WiiUpdate
{
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;
// 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"));
// 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) {
Core::QueueHostJob(
[&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')));
},
true);
return !was_cancelled.IsSet();
},
region);
Core::QueueHostJob([&dialog] { dialog.close(); }, true);
return res;
});
dialog.exec();
switch (result.get())
{
case WiiUtils::UpdateResult::Succeeded:
QMessageBox::information(parent, QObject::tr("Update completed"),
QObject::tr("The emulated Wii console has been updated."));
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
break;
case WiiUtils::UpdateResult::AlreadyUpToDate:
QMessageBox::information(parent, QObject::tr("Update completed"),
QObject::tr("The emulated Wii console is already up-to-date."));
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
break;
case WiiUtils::UpdateResult::ServerFailed:
QMessageBox::critical(parent, QObject::tr("Update failed"),
QObject::tr("Could not download update information from Nintendo. "
"Please check your Internet connection and try again."));
break;
case WiiUtils::UpdateResult::DownloadFailed:
QMessageBox::critical(parent, QObject::tr("Update failed"),
QObject::tr("Could not download update files from Nintendo. "
"Please check your Internet connection and try again."));
break;
case WiiUtils::UpdateResult::ImportFailed:
QMessageBox::critical(parent, QObject::tr("Update failed"),
QObject::tr("Could not install an update to the Wii system memory. "
"Please refer to logs for more information."));
break;
case WiiUtils::UpdateResult::Cancelled:
QMessageBox::warning(
parent, QObject::tr("Update cancelled"),
QObject::tr("The update has been cancelled. It is strongly recommended to "
"finish it in order to avoid inconsistent system software versions."));
break;
}
}
}; // namespace WiiUpdate

View File

@ -6,9 +6,9 @@
#include <string>
// Small utility functions for common Wii related tasks.
class QWidget;
namespace WiiUtils
namespace WiiUpdate
{
bool InstallWAD(const std::string& wad_path);
}
void PerformOnlineUpdate(const std::string& region, QWidget* parent = nullptr);
}; // namespace WiiUpdate

View File

@ -345,6 +345,7 @@ private:
void OnUninstallWAD(wxCommandEvent& event);
void OnImportBootMiiBackup(wxCommandEvent& event);
void OnExtractCertificates(wxCommandEvent& event);
void OnPerformOnlineWiiUpdate(wxCommandEvent& event);
void OnFifoPlayer(wxCommandEvent& event);
void OnConnectWiimote(wxCommandEvent& event);
void GameListChanged(wxCommandEvent& event);

View File

@ -3,8 +3,11 @@
// Refer to the license.txt file included.
#include <array>
#include <chrono>
#include <cinttypes>
#include <cstdarg>
#include <cstdio>
#include <future>
#include <mutex>
#include <string>
#include <vector>
@ -18,7 +21,6 @@
#include <wx/panel.h>
#include <wx/progdlg.h>
#include <wx/statusbr.h>
#include <wx/thread.h>
#include <wx/toolbar.h>
#include <wx/toplevel.h>
@ -54,6 +56,7 @@
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "Core/State.h"
#include "Core/WiiUtils.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h"
@ -87,7 +90,6 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "UICommon/UICommon.h"
#include "UICommon/WiiUtils.h"
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoBackendBase.h"
@ -182,6 +184,12 @@ void CFrame::BindMenuBarEvents()
Bind(wxEVT_MENU, &CFrame::OnLoadWiiMenu, this, IDM_LOAD_WII_MENU);
Bind(wxEVT_MENU, &CFrame::OnImportBootMiiBackup, this, IDM_IMPORT_NAND);
Bind(wxEVT_MENU, &CFrame::OnExtractCertificates, this, IDM_EXTRACT_CERTIFICATES);
for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_CURRENT, IDM_PERFORM_ONLINE_UPDATE_EUR,
IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR,
IDM_PERFORM_ONLINE_UPDATE_USA})
{
Bind(wxEVT_MENU, &CFrame::OnPerformOnlineWiiUpdate, this, idm);
}
Bind(wxEVT_MENU, &CFrame::OnFifoPlayer, this, IDM_FIFOPLAYER);
Bind(wxEVT_MENU, &CFrame::OnConnectWiimote, this, IDM_CONNECT_WIIMOTE1, IDM_CONNECT_BALANCEBOARD);
@ -1293,6 +1301,93 @@ void CFrame::OnExtractCertificates(wxCommandEvent& WXUNUSED(event))
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
}
static std::string GetUpdateRegionFromIdm(int idm)
{
switch (idm)
{
case IDM_PERFORM_ONLINE_UPDATE_EUR:
return "EUR";
case IDM_PERFORM_ONLINE_UPDATE_JPN:
return "JPN";
case IDM_PERFORM_ONLINE_UPDATE_KOR:
return "KOR";
case IDM_PERFORM_ONLINE_UPDATE_USA:
return "USA";
case IDM_PERFORM_ONLINE_UPDATE_CURRENT:
default:
return "";
}
}
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;
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:
wxMessageBox(_("The emulated Wii console has been updated."), _("Update completed"),
wxOK | wxICON_INFORMATION);
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
break;
case WiiUtils::UpdateResult::AlreadyUpToDate:
wxMessageBox(_("The emulated Wii console is already up-to-date."), _("Update completed"),
wxOK | wxICON_INFORMATION);
DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX));
break;
case WiiUtils::UpdateResult::ServerFailed:
wxMessageBox(_("Could not download update information from Nintendo. "
"Please check your Internet connection and try again."),
_("Update failed"), wxOK | wxICON_ERROR);
break;
case WiiUtils::UpdateResult::DownloadFailed:
wxMessageBox(_("Could not download update files from Nintendo. "
"Please check your Internet connection and try again."),
_("Update failed"), wxOK | wxICON_ERROR);
break;
case WiiUtils::UpdateResult::ImportFailed:
wxMessageBox(_("Could not install an update to the Wii system memory. "
"Please refer to logs for more information."),
_("Update failed"), wxOK | wxICON_ERROR);
break;
case WiiUtils::UpdateResult::Cancelled:
wxMessageBox(_("The update has been cancelled. It is strongly recommended to "
"finish it in order to avoid inconsistent system software versions."),
_("Update cancelled"), wxOK | wxICON_WARNING);
break;
}
UpdateLoadWiiMenuItem();
}
void CFrame::UpdateLoadWiiMenuItem() const
{
GetMenuBar()->Refresh(true, nullptr);
@ -1492,10 +1587,6 @@ void CFrame::UpdateGUI()
GetMenuBar()
->FindItem(IDM_LOAD_GC_IPL_EUR)
->Enable(!Initialized && File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR)));
if (DiscIO::NANDContentManager::Access()
.GetNANDLoader(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT)
.IsValid())
GetMenuBar()->FindItem(IDM_LOAD_WII_MENU)->Enable(!Initialized);
// Tools
GetMenuBar()->FindItem(IDM_CHEATS)->Enable(SConfig::GetInstance().bEnableCheats);

View File

@ -104,6 +104,11 @@ enum
IDM_LIST_UNINSTALL_WAD,
IDM_IMPORT_NAND,
IDM_EXTRACT_CERTIFICATES,
IDM_PERFORM_ONLINE_UPDATE_CURRENT,
IDM_PERFORM_ONLINE_UPDATE_EUR,
IDM_PERFORM_ONLINE_UPDATE_JPN,
IDM_PERFORM_ONLINE_UPDATE_KOR,
IDM_PERFORM_ONLINE_UPDATE_USA,
IDM_FIFOPLAYER,
IDM_LOAD_GC_IPL_JAP,
IDM_LOAD_GC_IPL_USA,

View File

@ -235,6 +235,16 @@ wxMenu* MainMenuBar::CreateToolsMenu() const
tools_menu->Append(IDM_LOAD_WII_MENU, dummy_string);
tools_menu->Append(IDM_IMPORT_NAND, _("Import BootMii NAND Backup..."));
tools_menu->Append(IDM_EXTRACT_CERTIFICATES, _("Extract Certificates from NAND"));
auto* const online_update_menu = new wxMenu;
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_CURRENT, _("Current Region"));
online_update_menu->AppendSeparator();
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_EUR, _("Europe"));
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_JPN, _("Japan"));
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_KOR, _("Korean"));
online_update_menu->Append(IDM_PERFORM_ONLINE_UPDATE_USA, _("United States"));
tools_menu->AppendSubMenu(
online_update_menu, _("Perform Online System Update"),
_("Update the Wii system software to the latest version from Nintendo."));
tools_menu->AppendSeparator();
tools_menu->AppendSubMenu(wiimote_menu, _("Connect Wii Remotes"));
@ -562,8 +572,6 @@ void MainMenuBar::RefreshSaveStateMenuLabels() const
void MainMenuBar::RefreshWiiToolsLabels() const
{
RefreshWiiSystemMenuLabel();
// The Install WAD option should not be enabled while emulation is running, because
// having unexpected title changes can confuse emulated software; and of course, this is
// not possible on a real Wii and won't be if we have IOS LLE (or simply more accurate IOS HLE).
@ -571,10 +579,26 @@ void MainMenuBar::RefreshWiiToolsLabels() const
// For similar reasons, it should not be possible to export or import saves, because this can
// result in the emulated software being confused, or even worse, exported saves having
// inconsistent data.
for (const int index : {IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE,
IDM_IMPORT_NAND, IDM_EXTRACT_CERTIFICATES})
const bool enable_wii_tools = !Core::IsRunning() || !SConfig::GetInstance().bWii;
for (const int index :
{IDM_MENU_INSTALL_WAD, IDM_EXPORT_ALL_SAVE, IDM_IMPORT_SAVE, IDM_IMPORT_NAND,
IDM_EXTRACT_CERTIFICATES, IDM_LOAD_WII_MENU, IDM_PERFORM_ONLINE_UPDATE_CURRENT,
IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN, IDM_PERFORM_ONLINE_UPDATE_KOR,
IDM_PERFORM_ONLINE_UPDATE_USA})
{
FindItem(index)->Enable(!Core::IsRunning() || !SConfig::GetInstance().bWii);
FindItem(index)->Enable(enable_wii_tools);
}
if (enable_wii_tools)
RefreshWiiSystemMenuLabel();
}
void MainMenuBar::EnableUpdateMenu(UpdateMenuMode mode) const
{
FindItem(IDM_PERFORM_ONLINE_UPDATE_CURRENT)->Enable(mode == UpdateMenuMode::CurrentRegionOnly);
for (const int idm : {IDM_PERFORM_ONLINE_UPDATE_EUR, IDM_PERFORM_ONLINE_UPDATE_JPN,
IDM_PERFORM_ONLINE_UPDATE_KOR, IDM_PERFORM_ONLINE_UPDATE_USA})
{
FindItem(idm)->Enable(mode == UpdateMenuMode::SpecificRegionsOnly);
}
}
@ -591,11 +615,13 @@ void MainMenuBar::RefreshWiiSystemMenuLabel() const
const wxString version_string = StrToWxStr(DiscIO::GetSysMenuVersionString(version_number));
item->Enable();
item->SetItemLabel(wxString::Format(_("Load Wii System Menu %s"), version_string));
EnableUpdateMenu(UpdateMenuMode::CurrentRegionOnly);
}
else
{
item->Enable(false);
item->SetItemLabel(_("Load Wii System Menu"));
EnableUpdateMenu(UpdateMenuMode::SpecificRegionsOnly);
}
}

View File

@ -48,6 +48,12 @@ private:
void RefreshSaveStateMenuLabels() const;
void RefreshWiiToolsLabels() const;
void RefreshWiiSystemMenuLabel() const;
enum class UpdateMenuMode
{
CurrentRegionOnly,
SpecificRegionsOnly,
};
void EnableUpdateMenu(UpdateMenuMode mode) const;
void ClearSavedPerspectivesMenu() const;
void PopulateSavedPerspectivesMenu(const std::vector<std::string>& label_names) const;

View File

@ -3,7 +3,6 @@ set(SRCS
Disassembler.cpp
UICommon.cpp
USBUtils.cpp
WiiUtils.cpp
)
if(USE_X11)

View File

@ -50,14 +50,12 @@
<ClCompile Include="USBUtils.cpp">
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="WiiUtils.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CommandLineParse.h" />
<ClInclude Include="UICommon.h" />
<ClInclude Include="Disassembler.h" />
<ClInclude Include="USBUtils.h" />
<ClInclude Include="WiiUtils.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ExternalsDir)cpp-optparse\cpp-optparse.vcxproj">
@ -67,4 +65,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -1,76 +0,0 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "UICommon/WiiUtils.h"
#include "Common/CommonTypes.h"
#include "Common/MsgHandler.h"
#include "Core/ConfigManager.h"
#include "Core/IOS/ES/ES.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/IOS.h"
#include "DiscIO/NANDContentLoader.h"
#include "DiscIO/WiiWad.h"
namespace WiiUtils
{
bool InstallWAD(const std::string& wad_path)
{
const DiscIO::WiiWAD wad{wad_path};
if (!wad.IsValid())
{
PanicAlertT("WAD installation failed: The selected file is not a valid WAD.");
return false;
}
const auto tmd = wad.GetTMD();
IOS::HLE::Kernel ios;
const auto es = ios.GetES();
IOS::HLE::Device::ES::Context context;
IOS::HLE::ReturnCode ret;
const bool checks_enabled = SConfig::GetInstance().m_enable_signature_checks;
while ((ret = es->ImportTicket(wad.GetTicket().GetBytes(), wad.GetCertificateChain())) < 0 ||
(ret = es->ImportTitleInit(context, tmd.GetBytes(), wad.GetCertificateChain())) < 0)
{
if (checks_enabled && ret == IOS::HLE::IOSC_FAIL_CHECKVALUE &&
AskYesNoT("This WAD has not been signed by Nintendo. Continue to import?"))
{
SConfig::GetInstance().m_enable_signature_checks = false;
continue;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
PanicAlertT("WAD installation failed: Could not initialise title import.");
return false;
}
SConfig::GetInstance().m_enable_signature_checks = checks_enabled;
const bool contents_imported = [&]() {
const u64 title_id = tmd.GetTitleId();
for (const IOS::ES::Content& content : tmd.GetContents())
{
const std::vector<u8> data = wad.GetContent(content.index);
if (es->ImportContentBegin(context, title_id, content.id) < 0 ||
es->ImportContentData(context, 0, data.data(), static_cast<u32>(data.size())) < 0 ||
es->ImportContentEnd(context, 0) < 0)
{
PanicAlertT("WAD installation failed: Could not import content %08x.", content.id);
return false;
}
}
return true;
}();
if ((contents_imported && es->ImportTitleDone(context) < 0) ||
(!contents_imported && es->ImportTitleCancel(context) < 0))
{
PanicAlertT("WAD installation failed: Could not finalise title import.");
return false;
}
DiscIO::NANDContentManager::Access().ClearCache();
return true;
}
}